Skip to content

Comments

better ai#1724

Merged
MrgSub merged 2 commits intostagingfrom
07-13-better_ai
Jul 13, 2025
Merged

better ai#1724
MrgSub merged 2 commits intostagingfrom
07-13-better_ai

Conversation

@MrgSub
Copy link
Collaborator

@MrgSub MrgSub commented Jul 13, 2025

Description

Refactored the AI chat component and email assistant to improve performance, reliability, and user experience. This PR introduces a new inboxRag tool for natural language email search, replaces the previous listThreads implementation, and enhances the prompt design for more efficient inbox management.


Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • ⚡ Performance improvement

Areas Affected

  • Email Integration (Gmail, IMAP, etc.)
  • User Interface/Experience
  • API Endpoints

Testing Done

  • Manual testing performed

Checklist

  • I have performed a self-review of my code
  • My changes generate no new warnings

Additional Notes

Key improvements:

  • Added ThreadPreview component to replace the previous thread rendering approach
  • Refactored ToolResponse to use specialized subcomponents for different tool types
  • Updated AI prompts with better instructions for multi-step and parallel operations
  • Switched backend models to Claude 3.5 for improved reasoning capabilities
  • Fixed key prop issues in attachment rendering to prevent React warnings
  • Increased agent max steps from 5 to 10 for more complex operations
  • Added better markdown styling support for lists and other elements

Summary by CodeRabbit

  • New Features

    • Introduced advanced AI-powered inbox search and organization with new tools and richer assistant guidance.
    • Added support for Anthropic AI models for chat and search tasks.
    • Enabled new streaming tool for natural language inbox search.
  • Improvements

    • Enhanced AI assistant prompt with detailed instructions, expanded use cases, and improved communication style.
    • Modularized and streamlined AI chat component for clearer tool response rendering and better markdown styling.
    • Increased chat step limit in the AI sidebar for longer conversations.
    • Improved uniqueness of attachment keys to prevent display issues.
  • Bug Fixes

    • Corrected parameter naming for thread and draft listing to ensure accurate pagination.
  • Chores

    • Updated dependencies and environment variables for improved configuration and model support.
    • Cleaned up unused code and imports for better maintainability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 13, 2025

Walkthrough

This set of changes introduces a new AI-powered thread search tool (InboxRag), removes the deprecated ListThreads tool, and refactors both backend and frontend to support the new toolset. It also rewrites AI prompt instructions, updates key naming conventions, and improves modularity and consistency in tool response rendering and agent abstractions.

Changes

File(s) Change Summary
apps/mail/components/create/ai-chat.tsx Refactored tool response rendering; modularized into subcomponents; rewrote thread preview logic; improved markdown styling; cleaned up unused code.
apps/mail/components/mail/mail-display.tsx Changed React keys for attachments to composite keys for uniqueness.
apps/mail/components/mail/select-all-checkbox.tsx Changed thread listing query parameter from max to maxResults.
apps/mail/components/ui/ai-sidebar.tsx Increased maxSteps for chat; consolidated hook destructuring.
apps/mail/lib/prompts.ts Rewrote AI chat prompt for detailed assistant instructions; updated function signature to only take threadId.
apps/mail/types/tools.ts Added InboxRag to Tools enum.
apps/server/package.json Added @ai-sdk/anthropic dependency.
apps/server/src/lib/prompts.ts Rewrote and expanded AI prompt; removed currentFolder and currentFilter params.
apps/server/src/routes/agent/orchestrator.ts Added support for streaming InboxRag tool; updated constructor and tool orchestration logic.
apps/server/src/routes/agent/tools.ts Refactored all tools to use ZeroAgent abstraction; replaced getEmail/listEmails logic; added InboxRag tool.
apps/server/src/routes/ai.ts Removed all ListThreads tool logic and API handling; commented out tool definition.
apps/server/src/routes/chat.ts Switched to Anthropic model; added inboxRag and searchThreads methods; updated thread storage and logging.
apps/server/src/trpc/routes/drafts.ts Renamed input parameter max to maxResults; updated type assertions.
apps/server/src/trpc/routes/mail.ts Renamed input parameter max to maxResults in thread listing.
apps/server/src/types.ts Removed ListThreads, added InboxRag to Tools enum.
apps/server/wrangler.jsonc Set DROP_AGENT_TABLES to false for local; added AUTORAG_ID variable.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AIChat (Frontend)
    participant Server
    participant ZeroAgent
    participant Autorag

    User->>AIChat: Search for emails (natural language)
    AIChat->>Server: Request thread search (InboxRag tool)
    Server->>ZeroAgent: Call searchThreads({ query })
    ZeroAgent->>Autorag: inboxRag(query)
    Autorag-->>ZeroAgent: threadIds[]
    ZeroAgent-->>Server: threadIds[]
    Server-->>AIChat: threadIds[]
    AIChat->>AIChat: Render ThreadPreview for each threadId
Loading

Possibly related PRs

  • Mail-0/Zero#1198: Added initial ListThreads action handling to the AI router; this PR removes that logic, making them directly related.
  • Mail-0/Zero#1608: Refactored thread retrieval in ZeroAgent and AgentRpcDO; both PRs focus on thread search abstraction and retrieval logic.
  • Mail-0/Zero#1676: Both PRs modify the DROP_AGENT_TABLES variable in environment configs, though in different contexts.

Suggested reviewers

  • ahmetskilinc

Poem

🐇✨
A hop, a leap, a search anew,
Old threads retire, as InboxRag grew.
Anthropic minds now guide the quest,
With prompts and tools refined for best.
Attachments keyed and prompts re-spun,
The agent’s work is never done!
Zero’s inbox—spring cleaning begun!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2e3727 and a139873.

📒 Files selected for processing (1)
  • apps/mail/components/create/ai-chat.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/mail/components/create/ai-chat.tsx
⏰ 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). (2)
  • GitHub Check: Cursor BugBot
  • GitHub Check: Analyze (javascript-typescript)
✨ Finishing Touches
  • 📝 Generate Docstrings

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Collaborator Author

MrgSub commented Jul 13, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

@MrgSub MrgSub marked this pull request as ready for review July 13, 2025 23:20
Copy link
Contributor

@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.

Greptile Summary

This PR represents a significant architectural shift in how AI functionality is implemented within the email system. The key changes revolve around replacing the traditional listThreads implementation with a new inboxRag tool that enables natural language search capabilities. This change is paired with a switch to Claude 3.5 for improved reasoning and several performance optimizations.

The main architectural changes include:

  1. Implementation of RAG (Retrieval Augmented Generation) for natural language email search
  2. Breaking down the monolithic ToolResponse component into specialized subcomponents
  3. Increasing agent max steps from 5 to 10 to support more complex operations
  4. Optimizing memory usage in Cloudflare Workers by using a tag-based approach instead of returning full thread data
  5. Adding parallel search capabilities that combine RAG with traditional search methods

These changes fundamentally improve how users can interact with their email through AI, moving from rigid listing operations to more natural language-based interactions while maintaining performance through careful optimization.

Confidence score: 3/5

  1. While the changes are well-structured, the complexity of AI integration requires careful monitoring
  2. The score reflects concerns about the switch to Claude 3.5 and potential performance implications of parallel search operations
  3. Key files needing attention:
    • apps/server/src/routes/chat.ts: New parallel search implementation needs load testing
    • apps/server/src/routes/agent/tools.ts: Memory optimization approach should be verified
    • apps/mail/components/create/ai-chat.tsx: Component refactoring may need performance profiling

16 files reviewed, 8 comments
Edit PR Review Bot Settings | Greptile

parameters: z.object({
query: z.string().describe('The query to search the inbox for'),
}),
execute: async ({ query }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

style: toolCallId not used in InboxRag but used in WebSearch - inconsistent pattern

Comment on lines +31 to 32
return agent.listDrafts({ q, maxResults, pageToken }) as Awaited<
ReturnType<MailManager['listDrafts']>
Copy link
Contributor

Choose a reason for hiding this comment

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

style: This still uses Awaited while getDraft doesn't - consider standardizing the approach across both endpoints

Comment on lines +159 to +203
// description: 'List threads',
// parameters: z.object({
// folder: z.string().default(FOLDERS.INBOX).describe('The folder to list threads from'),
// query: z.string().optional().describe('The query to filter threads by'),
// maxResults: z
// .number()
// .optional()
// .default(5)
// .describe('The maximum number of threads to return'),
// labelIds: z.array(z.string()).optional().describe('The label IDs to filter threads by'),
// pageToken: z.string().optional().describe('The page token to use for pagination'),
// }),
// execute: async (params) => {
// console.log('[DEBUG] listThreads', params);

// const result = await agent.listThreads({
// folder: params.folder,
// query: params.query,
// maxResults: params.maxResults,
// labelIds: params.labelIds,
// pageToken: params.pageToken,
// });
// const content = await Promise.all(
// result.threads.map(async (thread: any) => {
// const loadedThread = await agent.getThread(thread.id);
// return [
// {
// type: 'text' as const,
// text: `Subject: ${loadedThread.latest?.subject} | Received: ${loadedThread.latest?.receivedOn}`,
// },
// ];
// }),
// );
// return {
// content: content.length
// ? content.flat()
// : [
// {
// type: 'text' as const,
// text: 'No threads found',
// },
// ],
// };
// },
// }),
Copy link
Contributor

Choose a reason for hiding this comment

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

style: remove commented code block and document changes in version control instead

Comment on lines +265 to +267
<note>NEVER include markdown, XML tags or code formatting in the final response.</note>
<note>Do not use markdown formatting in your response.</note>
<note>NEVER use markdown lists (-, *, 1., etc.) in responses - use plain text instead.</note>
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Inconsistent duplication of markdown formatting rules across three lines. Consider consolidating into a single comprehensive rule.

const createLabel = (driver: MailManager) =>
const createLabel = (agent: ZeroAgent) =>
tool({
description: 'Create a new label with custom colors, if it does nto exist already',
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: typo in description: 'nto' should be 'not'

Suggested change
description: 'Create a new label with custom colors, if it does nto exist already',
description: 'Create a new label with custom colors, if it does not exist already',

Comment on lines +396 to +401
[Tools.WebSearch]: tool({
description: 'Search the web for information using Perplexity AI',
parameters: z.object({
query: z.string().describe('The query to search the web for'),
}),
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: WebSearch tool defined twice - this implementation has no execute function while the one above does. Remove duplicate definition.

Comment on lines 560 to +564
<rule>When using the listThreads tool, respond only with "Here are the emails I found" without providing any details about the emails.</rule>
<rule>Be direct and concise - avoid unnecessary preamble or explanations unless requested.</rule>
<rule>Take action when asked, don't just describe what you could do.</rule>
<rule>For complex tasks, break them down and execute systematically without over-explaining each step.</rule>
<rule>When multiple search patterns are needed, execute them in parallel for efficiency.</rule>
Copy link
Contributor

Choose a reason for hiding this comment

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

style: New response rules improve efficiency but rule about listThreads tool is now obsolete since tool was removed

Suggested change
<rule>When using the listThreads tool, respond only with "Here are the emails I found" without providing any details about the emails.</rule>
<rule>Be direct and concise - avoid unnecessary preamble or explanations unless requested.</rule>
<rule>Take action when asked, don't just describe what you could do.</rule>
<rule>For complex tasks, break them down and execute systematically without over-explaining each step.</rule>
<rule>When multiple search patterns are needed, execute them in parallel for efficiency.</rule>
<rule>Be direct and concise - avoid unnecessary preamble or explanations unless requested.</rule>
<rule>Take action when asked, don't just describe what you could do.</rule>
<rule>For complex tasks, break them down and execute systematically without over-explaining each step.</rule>
<rule>When multiple search patterns are needed, execute them in parallel for efficiency.</rule>

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

cubic found 2 issues across 16 files. Review them in cubic.dev

React with 👍 or 👎 to teach cubic. Tag @cubic-dev-ai to give specific feedback.

[Tools.BulkDelete]: bulkDelete(agent),
[Tools.BulkArchive]: bulkArchive(agent),
[Tools.DeleteLabel]: deleteLabel(agent),
[Tools.WebSearch]: tool({
Copy link
Contributor

Choose a reason for hiding this comment

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

The tool is declared without an execute method, so calling Tools.WebSearch will throw at runtime because the returned object is missing the required execute function.

Suggested change
[Tools.WebSearch]: tool({
[Tools.WebSearch]: webSearch(),

<note>Do not use markdown formatting in your response.</note>
<note>NEVER use markdown lists (-, *, 1., etc.) in responses - use plain text instead.</note>

# Agency & Problem-Solving Approach
Copy link
Contributor

Choose a reason for hiding this comment

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

Prompt engineering logic for agent behavior, tool chaining, and communication style is duplicated from apps/mail/lib/prompts.ts. This includes the 'Agency & Problem-Solving Approach', 'Systematic Approach', 'Tool Chaining Excellence', and 'communicationStyle' sections.

Copy link
Contributor

@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: 5

🔭 Outside diff range comments (5)
apps/mail/components/mail/mail-display.tsx (1)

1643-1670: Separator condition uses the wrong index – attachments get the outer e-mail index instead of their own
Inside emailData.attachments.map(...) the callback only receives attachment, so index resolves to the outer prop.
This makes the separator logic incorrect for any thread where the e-mail isn’t the first one.

-{emailData?.attachments.map((attachment) => (
+{emailData?.attachments.map((attachment, idx) => (
   <div
-    key={`${attachment.filename}-${attachment.attachmentId}`}
+    key={`${attachment.attachmentId}-${attachment.filename}`}  // keeps key order consistent
     className="flex"
   >
     …
-    {index < (emailData?.attachments?.length || 0) - 1 && (
+    {idx < (emailData?.attachments?.length || 0) - 1 && (
       <div className="m-auto h-2 w-[1px] bg-[#E0E0E0] dark:bg-[#424242]" />
     )}
   </div>
 ))}

This passes the attachment’s own index (idx) and aligns the key ordering with the earlier change.

apps/server/src/lib/prompts.ts (1)

486-486: Remove reference to deprecated listThreads tool

This line references the listThreads tool which has been removed from the codebase. This instruction is now obsolete and should be removed.

-<rule>When using the listThreads tool, respond only with "Here are the emails I found" without providing any details about the emails.</rule>
apps/mail/components/create/ai-chat.tsx (1)

20-69: Add loading and error states to ThreadPreview

The component silently returns null when data is not available, which doesn't differentiate between loading, error, and no-data states. This could confuse users.

Consider adding loading and error states:

const ThreadPreview = ({ threadId }: { threadId: string }) => {
  const [, setThreadId] = useQueryState('threadId');
-  const { data: getThread } = useThread(threadId);
+  const { data: getThread, isLoading, error } = useThread(threadId);
  const [, setIsFullScreen] = useQueryState('isFullScreen');

  const handleClick = () => {
    setThreadId(threadId);
    setIsFullScreen(null);
  };

+  if (isLoading) {
+    return (
+      <div className="flex items-center justify-center p-4">
+        <TextShimmer className="text-xs">Loading thread...</TextShimmer>
+      </div>
+    );
+  }
+
+  if (error) {
+    return (
+      <div className="text-sm text-red-500 p-2">
+        Failed to load thread
+      </div>
+    );
+  }
+
  if (!getThread?.latest) return null;
apps/mail/lib/prompts.ts (1)

7-110: Consider extracting shared color constants

The colors array is duplicated from the server-side code. This creates a maintenance burden where updates need to be made in multiple places.

Consider creating a shared constants file that both client and server can import:

// In a shared constants file (e.g., shared/constants/colors.ts)
export const LABEL_COLORS = [
  '#000000',
  '#434343',
  // ... rest of colors
];
apps/server/src/routes/agent/tools.ts (1)

236-294: Inconsistent error handling across tools

The sendEmail tool has comprehensive error handling with try-catch and logging, but other tools that make async calls to the agent don't have any error handling. This inconsistency could make debugging difficult.

Consider adding consistent error handling to all tools that make external calls:

const markAsRead = (agent: ZeroAgent) =>
  tool({
    description: 'Mark emails as read',
    parameters: z.object({
      threadIds: z.array(z.string()).describe('The IDs of the threads to mark as read'),
    }),
    execute: async ({ threadIds }) => {
+      try {
        await agent.markAsRead(threadIds);
        return { threadIds, success: true };
+      } catch (error) {
+        console.error('Error marking emails as read:', error);
+        throw new Error(
+          'Failed to mark emails as read: ' + (error instanceof Error ? error.message : String(error)),
+        );
+      }
    },
  });
♻️ Duplicate comments (1)
apps/mail/lib/prompts.ts (1)

486-486: Remove reference to deprecated listThreads tool

Same issue as in the server-side prompt - this line references the removed listThreads tool.

🧹 Nitpick comments (6)
apps/mail/components/mail/mail-display.tsx (1)

298-310: Composite key reduces collisions – keep ordering consistent & guard against undefined
Switching to a composite key is a solid fix for the “duplicate key” warning.
For consistency (and to avoid unnecessary re-mounts) consider using the same ordering everywhere in this file – here it is attachmentId-filename, while the inline attachment list uses filename-attachmentId. Also, if either property can be undefined, prepend a fallback ('') to avoid keys such as "undefined-".

apps/server/src/routes/ai.ts (1)

159-203: Consider removing commented code instead of keeping it.

The ListThreads tool has been deprecated in favor of InboxRag. Since this appears to be an intentional architectural change, consider removing the commented code entirely to keep the codebase clean. If needed for reference, version control history can be used.

-      //     description: 'List threads',
-      //     parameters: z.object({
-      //       folder: z.string().default(FOLDERS.INBOX).describe('The folder to list threads from'),
-      //       query: z.string().optional().describe('The query to filter threads by'),
-      //       maxResults: z
-      //         .number()
-      //         .optional()
-      //         .default(5)
-      //         .describe('The maximum number of threads to return'),
-      //       labelIds: z.array(z.string()).optional().describe('The label IDs to filter threads by'),
-      //       pageToken: z.string().optional().describe('The page token to use for pagination'),
-      //     }),
-      //     execute: async (params) => {
-      //       console.log('[DEBUG] listThreads', params);
-
-      //       const result = await agent.listThreads({
-      //         folder: params.folder,
-      //         query: params.query,
-      //         maxResults: params.maxResults,
-      //         labelIds: params.labelIds,
-      //         pageToken: params.pageToken,
-      //       });
-      //       const content = await Promise.all(
-      //         result.threads.map(async (thread: any) => {
-      //           const loadedThread = await agent.getThread(thread.id);
-      //           return [
-      //             {
-      //               type: 'text' as const,
-      //               text: `Subject: ${loadedThread.latest?.subject} | Received: ${loadedThread.latest?.receivedOn}`,
-      //             },
-      //           ];
-      //         }),
-      //       );
-      //       return {
-      //         content: content.length
-      //           ? content.flat()
-      //           : [
-      //               {
-      //                 type: 'text' as const,
-      //                 text: 'No threads found',
-      //               },
-      //             ],
-      //       };
-      //     },
-      //   }),
apps/server/src/routes/chat.ts (1)

590-590: Remove or guard debug logging before production.

Debug logging of streamed response chunks could be verbose and impact performance in production.

-        console.log('reply', body);
+        if (env.DEBUG) console.log('reply', body);
apps/server/src/lib/prompts.ts (2)

342-345: Use consistent HTML entity encoding

The HTML entities &lt; and &gt; are used here, but elsewhere in the prompt you use literal < and > characters. For consistency, either use HTML entities throughout or use literal characters throughout.

-<description>
-  Returns ONLY the tag &lt;thread id="{id}"/&gt;.  
-  The client will resolve the thread locally, so do **not** expect the tool
-  to return any email data.
-</description>
+<description>
+  Returns ONLY the tag <thread id="{id}"/>.  
+  The client will resolve the thread locally, so do **not** expect the tool
+  to return any email data.
+</description>

329-791: Consider the impact of prompt length on token usage

The prompt has grown to over 400 lines with detailed instructions, examples, and use cases. While comprehensive documentation is valuable, this could significantly impact token usage and costs, especially for frequent AI interactions.

Consider:

  • Moving some of the static content (like use case examples) to a separate documentation system
  • Dynamically including only relevant use cases based on user context
  • Creating a more concise version for standard operations
apps/server/src/routes/agent/tools.ts (1)

157-197: Remove commented-out code

Large blocks of commented code should be removed rather than left in the codebase. If this code might be needed later, it can be retrieved from version control history.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 414a44d and c2e3727.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • apps/mail/components/create/ai-chat.tsx (5 hunks)
  • apps/mail/components/mail/mail-display.tsx (2 hunks)
  • apps/mail/components/mail/select-all-checkbox.tsx (1 hunks)
  • apps/mail/components/ui/ai-sidebar.tsx (2 hunks)
  • apps/mail/lib/prompts.ts (2 hunks)
  • apps/mail/types/tools.ts (1 hunks)
  • apps/server/package.json (1 hunks)
  • apps/server/src/lib/prompts.ts (5 hunks)
  • apps/server/src/routes/agent/orchestrator.ts (5 hunks)
  • apps/server/src/routes/agent/tools.ts (8 hunks)
  • apps/server/src/routes/ai.ts (1 hunks)
  • apps/server/src/routes/chat.ts (17 hunks)
  • apps/server/src/trpc/routes/drafts.ts (1 hunks)
  • apps/server/src/trpc/routes/mail.ts (3 hunks)
  • apps/server/src/types.ts (1 hunks)
  • apps/server/wrangler.jsonc (1 hunks)
🧰 Additional context used
🧠 Learnings (9)
apps/mail/components/mail/select-all-checkbox.tsx (2)
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:331-331
Timestamp: 2025-06-28T03:56:09.376Z
Learning: In apps/server/src/trpc/routes/mail.ts, the user indicated they are not using ISO format for the scheduleAt parameter, despite the frontend code showing toISOString() usage in the ScheduleSendPicker component.
apps/server/package.json (1)
Learnt from: JagjeevanAK
PR: Mail-0/Zero#1583
File: apps/docs/package.json:1-0
Timestamp: 2025-07-01T12:53:32.495Z
Learning: The Zero project prefers to handle dependency updates through automated tools like Dependabot rather than immediate manual updates, allowing for proper testing and validation through their established workflow.
apps/mail/components/ui/ai-sidebar.tsx (1)
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.
apps/server/src/trpc/routes/drafts.ts (2)
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:331-331
Timestamp: 2025-06-28T03:56:09.376Z
Learning: In apps/server/src/trpc/routes/mail.ts, the user indicated they are not using ISO format for the scheduleAt parameter, despite the frontend code showing toISOString() usage in the ScheduleSendPicker component.
apps/mail/components/mail/mail-display.tsx (2)
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.
apps/server/src/routes/ai.ts (1)
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.
apps/server/src/trpc/routes/mail.ts (2)
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:331-331
Timestamp: 2025-06-28T03:56:09.376Z
Learning: In apps/server/src/trpc/routes/mail.ts, the user indicated they are not using ISO format for the scheduleAt parameter, despite the frontend code showing toISOString() usage in the ScheduleSendPicker component.
apps/server/src/routes/chat.ts (1)
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.
apps/server/src/routes/agent/tools.ts (1)
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.
🧬 Code Graph Analysis (4)
apps/server/src/routes/agent/orchestrator.ts (1)
apps/server/src/lib/server-utils.ts (1)
  • getZeroAgent (13-18)
apps/server/src/trpc/routes/mail.ts (2)
apps/server/src/lib/utils.ts (2)
  • defaultPageSize (112-112)
  • FOLDERS (43-50)
apps/server/src/lib/server-utils.ts (1)
  • getZeroAgent (13-18)
apps/server/src/lib/prompts.ts (1)
apps/mail/lib/prompts.ts (1)
  • AiChatPrompt (255-717)
apps/mail/lib/prompts.ts (1)
apps/server/src/lib/prompts.ts (3)
  • AiChatPrompt (329-791)
  • getCurrentDateContext (112-112)
  • colors (7-110)
🪛 Biome (1.9.4)
apps/mail/components/create/ai-chat.tsx

[error] 293-294: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ 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). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Cursor BugBot
🔇 Additional comments (20)
apps/server/wrangler.jsonc (1)

103-103: Turning DROP_AGENT_TABLES off will persist local DB state – double-check dev/test workflows

Changing the flag from "true" to "false" means tables will no longer be truncated on each local start-up.
If any migration, seed, or e2e test implicitly relied on a clean slate, you may now surface hard-to-reproduce “left-over data” bugs.

Run the local test suite once with the old setting and once with the new to ensure nothing depends on automatic purging.

apps/server/package.json (1)

22-22: LGTM! Dependency addition aligns with AI model switch.

The addition of the Anthropic SDK supports the PR's objective to switch from OpenAI to Claude 3.5. While the project typically prefers automated dependency updates through Dependabot, this manual addition is justified as part of the feature implementation.

apps/mail/components/mail/select-all-checkbox.tsx (1)

44-44: LGTM! Parameter rename improves API consistency.

The change from max to maxResults aligns with the backend API naming conventions mentioned in the summary, ensuring consistent parameter naming across the codebase.

apps/mail/types/tools.ts (2)

18-18: LGTM! New InboxRag tool added correctly.

The addition of the InboxRag enum member supports the new inbox search functionality mentioned in the PR objectives.


4-4: Inconsistency: ListThreads still present despite summary claiming removal.

The AI summary indicates that ListThreads was removed and replaced by InboxRag, but ListThreads is still present in the code. Please verify if this tool should be removed as part of this PR.

Likely an incorrect or invalid review comment.

apps/server/src/types.ts (2)

228-228: LGTM! Backend InboxRag tool added to match frontend.

The addition of InboxRag to the backend Tools enum correctly mirrors the frontend change and supports the new inbox search functionality.


212-229: Inconsistency: ListThreads removal not reflected in code.

The AI summary states that ListThreads was removed from the enum, but it's still present in the code. Please verify if the removal should be part of this PR or if the summary is incorrect.

Likely an incorrect or invalid review comment.

apps/mail/components/ui/ai-sidebar.tsx (2)

337-338: LGTM! Destructuring consolidation improves readability.

The consolidation of the destructuring assignment improves code readability without changing functionality.


356-356: LGTM! Increased maxSteps supports enhanced AI capabilities.

Doubling the maximum steps from 5 to 10 aligns with the PR objectives to enable more complex multi-step operations and supports the enhanced AI toolset including the new InboxRag functionality.

apps/server/src/trpc/routes/mail.ts (1)

62-95: LGTM! Consistent parameter naming applied.

The renaming of max to maxResults improves clarity and aligns with common API conventions. All usages have been updated consistently throughout the procedure.

apps/server/src/trpc/routes/drafts.ts (2)

17-17: Type assertion simplified correctly.

The removal of Awaited wrapper is appropriate since getDraft already returns a Promise. This simplifies the type assertion without changing behavior.


23-31: Parameter naming aligned with other routes.

The maxmaxResults rename maintains consistency with the changes in mail.ts.

apps/server/src/routes/agent/orchestrator.ts (2)

14-19: Connection context properly integrated.

The addition of connectionId to the orchestrator enables proper agent instantiation for the new InboxRag tool. Good implementation.


70-83: InboxRag tool implementation looks good.

The new streaming tool correctly:

  • Uses the stored connectionId to get the agent
  • Calls searchThreads with appropriate parameters
  • Returns only the threadIds array as specified in the description
apps/server/src/routes/chat.ts (5)

40-40: AI provider switch implemented correctly.

The migration from OpenAI to Anthropic is properly imported and will be used throughout the file.


906-910: Good addition of thread metadata.

Storing the threadId in customMetadata improves traceability and will help with debugging and monitoring.


1074-1112: Robust implementation of parallel search with fallback.

The searchThreads method excellently handles:

  • Parallel execution of autorag and raw search using Effect
  • Proper error handling with fallback to empty arrays
  • Clear source indication in the response
  • Graceful degradation when autorag is unavailable

This is a well-architected solution for the new search functionality.


1118-1121: Parameter rename completed consistently.

The maxmaxResults rename is properly applied with appropriate default value handling.


401-402: Model change and increased step limit look good.

The switch to Anthropic's Claude model and increasing max steps from 5 to 10 allows for more complex multi-step operations as mentioned in the PR objectives.

apps/mail/components/create/ai-chat.tsx (1)

145-153: Add null safety for regex match extraction

The regex match could return null if the pattern doesn't match, which would cause the optional chaining to fail.

-const match = result.match(/<thread id="([^"]+)" ?\/>/);
-if (match?.[1]) threadId = match[1];
+const match = result.match(/<thread id="([^"]+)" ?\/>/);
+if (match && match[1]) threadId = match[1];

Likely an incorrect or invalid review comment.

Comment on lines +107 to 108
"AUTORAG_ID": "",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

AUTORAG_ID added only to local env – will be undefined in staging / prod

The backend code that introduces InboxRag will likely read process.env.AUTORAG_ID.
Because the variable is absent in the staging and production vars blocks, any access there will return undefined, leading to runtime errors or silent mis-configuration.

Proposed fix – propagate the placeholder to all environments:

@@ "vars": {
   "THREAD_SYNC_LOOP": "true",
   "DISABLE_WORKFLOWS": "true",
+  "AUTORAG_ID": "",
 }

Repeat the same insertion in the production section.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"AUTORAG_ID": "",
},
// … elsewhere in apps/server/wrangler.jsonc …
"staging": {
"vars": {
"THREAD_SYNC_LOOP": "true",
"DISABLE_WORKFLOWS": "true",
+ "AUTORAG_ID": ""
}
},
"production": {
"vars": {
"THREAD_SYNC_LOOP": "true",
"DISABLE_WORKFLOWS": "true",
+ "AUTORAG_ID": ""
}
}
// … rest of file …
🤖 Prompt for AI Agents
In apps/server/wrangler.jsonc around lines 107 to 108, the AUTORAG_ID
environment variable is only set in the local environment, causing it to be
undefined in staging and production. To fix this, add the AUTORAG_ID key with an
appropriate placeholder value to the environment variable sections for both
staging and production, ensuring consistent availability across all
environments.

Comment on lines +255 to +717
export const AiChatPrompt = (threadId: string) =>
dedent`
<system>
<description>
You are an intelligent email management assistant with access to advanced Gmail operations.
Your goal is to help users organize their inbox efficiently by searching, analyzing, categorizing,
and performing relevant actions on their emails while preserving important content.
</description>

<capabilities>
<searchAnalysis>
<feature>Search email threads using complex queries (keywords, dates, senders, etc.)</feature>
<feature>Analyze subject lines, email bodies, and metadata</feature>
<feature>Classify emails by topic, importance, or action type</feature>
</searchAnalysis>
<labelManagement>
<feature>Create labels with specified names and colors</feature>
<feature>Retrieve existing labels and check for duplicates</feature>
<feature>Apply labels to threads intelligently based on context</feature>
<feature>Propose and manage label hierarchies based on usage patterns</feature>
</labelManagement>
<emailOrganization>
<feature>Archive emails not needing attention</feature>
<feature>Mark emails as read/unread based on user intent</feature>
<feature>Apply bulk actions where appropriate</feature>
<feature>Support Inbox Zero principles and encourage long-term hygiene</feature>
</emailOrganization>
</capabilities>

<tools>
<tool name="listThreads">
<description>Search for and retrieve up to 5 threads matching a query.</description>
${currentFolder ? `<note>If the user does not specify a folder, use the current folder: ${currentFolder || '...'}</note>` : ''}
${currentFilter ? `<note>If the user does not specify a filter, use this as the base filter then add your own filters: ${currentFilter || '...'}</note>` : ''}
<usageExample>listThreads({ query: "subject:invoice AND is:unread", maxResults: 5 })</usageExample>
</tool>
<tool name="getThread">
<description>Get a thread by ID</description>
<usageExample>getThread({ threadId: "..." })</usageExample>
</tool>
<tool name="archiveThreads">
<description>Archive specified email threads.</description>
<usageExample>archiveThreads({ threadIds: [...] })</usageExample>
</tool>
<tool name="markThreadsRead">
<description>Mark specified threads as read.</description>
<usageExample>markThreadsRead({ threadIds: [...] })</usageExample>
</tool>
<tool name="markThreadsUnread">
<description>Mark specified threads as unread.</description>
<usageExample>markThreadsUnread({ threadIds: [...] })</usageExample>
</tool>
<tool name="createLabel">
<description>Create a new label with custom colors if it does not already exist.</description>
<parameters>
<parameter name="name" type="string"/>
<parameter name="backgroundColor" type="string"/>
<parameter name="textColor" type="string"/>
</parameters>
<allowedColors>${colors.join(', ')}</allowedColors>
<usageExample>createLabel({ name: "Subscriptions", backgroundColor: "#FFB6C1", textColor: "#000000" })</usageExample>
</tool>
<tool name="addLabelsToThreads">
<description>Apply existing or newly created labels to specified threads.</description>
<usageExample>addLabelsToThreads({ threadIds: [...], labelIds: [...] })</usageExample>
</tool>
<tool name="getUserLabels">
<description>Fetch all labels currently available in the user’s account.</description>
<usageExample>getUserLabels()</usageExample>
</tool>
</tools>

<bestPractices>
<practice>Confirm with the user before applying changes to many emails.</practice>
<practice>Explain reasoning for label or organization suggestions.</practice>
<practice>Never delete emails or perform irreversible actions without explicit consent.</practice>
<practice>Use timestamps to prioritize and filter relevance.</practice>
<practice>Group related messages to propose efficient batch actions.</practice>
<practice>If the user refers to *“this thread”* or *“this email”*, use this ID: ${threadId} and <tool>getThread</tool> to retrieve context before proceeding.</practice>
<practice>When asked to apply a label, first use <tool>getUserLabels</tool> to check for existence. If the label exists, apply it with <tool>addLabelsToThreads</tool>. If it does not exist, create it with <tool>createLabel</tool>, then apply it.</practice>
<practice>Use *{text}* to emphasize text when replying to users.</practice>
<practice>Never create a label with any of these names: ${CATEGORY_IDS.join(', ')}.</practice>
</bestPractices>

<responseRules>
<rule>Do not include tool output in the visible reply to the user.</rule>
<rule>Avoid filler phrases like "Here is" or "I found".</rule>
</responseRules>

<useCases>
<useCase name="Subscriptions">
<trigger>User asks about bills, subscriptions, or recurring expenses.</trigger>
<examples>
<example>What subscriptions do I have?</example>
<example>How much am I paying for streaming?</example>
</examples>
<detection>
<clue>Domains like netflix.com, spotify.com, apple.com</clue>
<clue>Keywords: "your subscription", "monthly charge"</clue>
</detection>
<response>
List subscriptions with name, amount, and frequency. Sum monthly totals.
</response>
</useCase>

<useCase name="Newsletters">
<trigger>User refers to newsletters or digest-style emails.</trigger>
<examples>
<example>What newsletters am I subscribed to?</example>
</examples>
<detection>
<clue>Subjects containing: "newsletter", "read more", "digest"</clue>
<clue>Domains like substack.com, mailchimp.com</clue>
</detection>
<response>List newsletter sources and sample subject lines.</response>
</useCase>

<useCase name="Meetings">
<trigger>User asks about scheduled meetings or events.</trigger>
<examples>
<example>Do I have any meetings today?</example>
</examples>
<detection>
<clue>Keywords: "Zoom", "Google Meet", "calendar invite"</clue>
<clue>Domains: calendly.com, zoom.us</clue>
</detection>
<response>
List meeting title, time, date, and platform. Highlight today's events.
</response>
</useCase>

<useCase name="Topic Queries">
<trigger>User requests information about a specific topic, task, or event.</trigger>
<examples>
<example>Find emails about the hackathon.</example>
</examples>
<detection>
<clue>Match topic in subject, body, or participants</clue>
</detection>
<response>
Summarize relevant threads with participants and dates.
</response>
</useCase>

<useCase name="Attachments">
<trigger>User mentions needing documents, images, or files.</trigger>
<examples>
<example>Find the tax PDF from last week.</example>
</examples>
<detection>
<clue>Attachments with .pdf, .jpg, .docx extensions</clue>
</detection>
<response>
Provide filenames, senders, and sent dates.
</response>
</useCase>

<useCase name="Summaries">
<trigger>User asks for inbox activity summaries.</trigger>
<examples>
<example>What happened in my inbox this week?</example>
</examples>
<detection>
<clue>Date-based filtering with topic categorization</clue>
</detection>
<response>
Summarize messages by theme (meetings, personal, purchases, etc.).
</response>
</useCase>

<useCase name="Projects">
<trigger>User mentions project-specific work or collaboration.</trigger>
<examples>
<example>Find updates on the onboarding project.</example>
</examples>
<detection>
<clue>Work-related keywords like "task", "deadline", "update"</clue>
<clue>Emails from known teammates or domains</clue>
</detection>
<response>
Provide summary lines and senders of relevant messages.
</response>
</useCase>
</useCases>

<exampleRequests>
<request>"Organize unread newsletters with labels."</request>
<request>"Label this email as ‘Follow-Up’."</request>
<request>"Summarize important messages from last week."</request>
<request>"Show recent emails with receipts and invoices."</request>
<request>"Add a project tag to this thread."</request>
</exampleRequests>

<philosophy>
<goal>Reduce inbox clutter while preserving valuable content.</goal>
<goal>Support user-driven organization with automated assistance.</goal>
<goal>Ensure changes are safe, transparent, and user-approved.</goal>
</philosophy>
</system>

`;
<system>
<description>
You are Fred, an intelligent, safety-conscious email management assistant integrated with advanced Gmail operations.
Your goal is to help users achieve Inbox Zero and long-term inbox hygiene by intelligently searching, analyzing, categorizing, summarizing, labeling, and organizing their emails with minimal friction and maximal relevance.
Zero is a tool that has complete historical context of the user's inbox and can answer questions about the mailbox or a specific thread.
</description>

<current_date>${getCurrentDateContext()}</current_date>
<note>NEVER include markdown, XML tags or code formatting in the final response.</note>
<note>Do not use markdown formatting in your response.</note>
<note>NEVER use markdown lists (-, *, 1., etc.) in responses - use plain text instead.</note>

# Agency & Problem-Solving Approach

You take initiative when users ask for email management tasks, maintaining balance between:
1. Taking appropriate action when requested, including follow-up actions
2. Not surprising users with unasked actions (when users ask how to approach something, answer first before acting)
3. Being direct and efficient without unnecessary explanations unless requested

## Systematic Approach
For complex email management tasks, follow these steps:
1. Use tools strategically - combine searches, analysis, and actions efficiently
2. For multi-step tasks, break them down and tackle systematically
3. Use parallel tool execution when operations are independent (e.g., searching multiple topics simultaneously)
4. Chain tools logically: search → analyze → act → verify

## Tool Chaining Excellence
- **Parallel Execution**: When searching for different types of emails simultaneously, invoke multiple inboxRag calls
- **Sequential Chaining**: Search → getThread → analyze → action (label/archive/delete)
- **Batch Operations**: Collect thread IDs from searches, then apply bulk actions
- **Verification**: After bulk actions, confirm results and provide concise summary

<toolChainExamples>
<example name="Complex Organization">
User: "Find all newsletters from last month and organize them"
Approach:
1. inboxRag multiple parallel searches for newsletter patterns
2. getThread for sample threads to confirm classification
3. getUserLabels to check existing organization
4. createLabel if needed for newsletter organization
5. modifyLabels for batch labeling
6. bulkArchive to clean inbox
</example>

<example name="Multi-Category Cleanup">
User: "Clean up promotional emails and old receipts"
Approach:
1. Parallel inboxRag searches: promotions, receipts, shopping confirmations
2. Analyze patterns across results
3. Suggest organization strategy
4. Execute bulk actions with user confirmation
</example>

<example name="Investment Tracking">
User: "Help me track my investment emails"
Approach:
1. Ask clarifying questions about investment types/platforms
2. inboxRag with targeted searches based on user's specifics
3. getThread for key investment emails
4. Suggest labeling system for ongoing organization
5. Create labels and apply to relevant threads
</example>
</toolChainExamples>

<capabilities>
<searchAnalysis>
<feature>Understand natural language queries about email topics, timeframes, senders, attachments, and other metadata.</feature>
<feature>Construct advanced Gmail-compatible search filters covering fields like subject, body, sender, recipients, labels, and timestamps.</feature>
<feature>Support broad queries by intelligently identifying patterns and keywords.</feature>
</searchAnalysis>
<labelManagement>
<feature>Suggest, create, and manage nested or color-coded labels.</feature>
<feature>Check for label existence before creation to avoid duplication.</feature>
<feature>Apply labels with context-aware justification.</feature>
</labelManagement>
<emailOrganization>
<feature>Archive or triage non-urgent threads with user permission.</feature>
<feature>Mark threads as read/unread based on task urgency or user intent.</feature>
<feature>Apply batch operations safely across matching threads.</feature>
<feature>Balance automation with transparency, ensuring user trust and control.</feature>
</emailOrganization>
</capabilities>

<tools>
<tool name="${Tools.GetThread}">
<description>
Returns ONLY the tag &lt;thread id="{id}"/&gt;.
The client will resolve the thread locally, so do **not** expect the tool
to return any email data.
</description>
<usageExample>getThread({ id: "17c2318b9c1e44f6" })</usageExample>
</tool>

<tool name="${Tools.InboxRag}">
<description>Search the inbox for emails using natural language. Returns ONLY an array
of thread IDs (e.g. ["17c23…", "17c24…"]). Use getThread afterwards
to display or act on a specific email.</description>
<parameters>
<parameter name="query" type="string" />
</parameters>
<usageExample>inboxRag({ query: "emails about the project deadline" })</usageExample>
</tool>

<tool name="${Tools.WebSearch}">
<description>Search the web for information using Perplexity AI, use it for famous people, companies, and other things that are not in the user's inbox.</description>
<parameters>
<parameter name="query" type="string" />
</parameters>
<usageExample>webSearch({ query: "What is the weather in Tokyo?" })</usageExample>
<usageExample>webSearch({ query: "What is the stock price of Apple?" })</usageExample>
<usageExample>webSearch({ query: "Tell me about Sequoia Capital?" })</usageExample>
<usageExample>webSearch({ query: "What is YC / YCombinator?" })</usageExample>
</tool>

<tool name="${Tools.BulkDelete}">
<description>Delete an email thread when the user confirms it's no longer needed.</description>
<usageExample>bulkDelete({ threadIds: ["..."] })</usageExample>
</tool>

<tool name="${Tools.BulkArchive}">
<description>Archive an email thread.</description>
<usageExample>bulkArchive({ threadIds: ["..."] })</usageExample>
</tool>

<tool name="${Tools.ModifyLabels}">
<description>
Add and/or remove labels from a list of thread IDs. This tool can be used to batch apply organizational changes.
<note>First use getUserLabels to get the label IDs, then use those IDs in addLabels and removeLabels arrays. Do not use label names directly.</note>
</description>
<parameters>
<parameter name="threadIds" type="string[]" />
<parameter name="options" type="object">
<parameter name="addLabels" type="string[]" />
<parameter name="removeLabels" type="string[]" />
</parameter>
</parameters>
<usageExample>
// First get label IDs
const labels = await getUserLabels();
const followUpLabel = labels.find(l => l.name === "Follow-Up")?.id;
const urgentLabel = labels.find(l => l.name === "Urgent")?.id;
const inboxLabel = labels.find(l => l.name === "INBOX")?.id;

modifyLabels({
threadIds: ["17892d1092d08b7e"],
options: {
addLabels: [followUpLabel, urgentLabel],
removeLabels: [inboxLabel]
}
})
</usageExample>
</tool>

<tool name="${Tools.MarkThreadsRead}">
<description>Mark threads as read to reduce inbox clutter when requested or inferred.</description>
<usageExample>markThreadsRead({ threadIds: [...] })</usageExample>
</tool>

<tool name="${Tools.MarkThreadsUnread}">
<description>Mark threads as unread if the user wants to follow up later or missed something important.</description>
<usageExample>markThreadsUnread({ threadIds: [...] })</usageExample>
</tool>

<tool name="${Tools.CreateLabel}">
<description>Create a new Gmail label if it doesn't already exist, with custom colors if specified.</description>
<parameters>
<parameter name="name" type="string"/>
<parameter name="backgroundColor" type="string"/>
<parameter name="textColor" type="string"/>
</parameters>
<allowedColors>${colors.join(', ')}</allowedColors>
<usageExample>createLabel({ name: "Follow-Up", backgroundColor: "#FFA500", textColor: "#000000" })</usageExample>
</tool>

<tool name="${Tools.DeleteLabel}">
<description>Delete a Gmail label by name.</description>
<parameters>
<parameter name="id" type="string"/>
</parameters>
<usageExample>deleteLabel({ id: "..." })</usageExample>
</tool>

<tool name="${Tools.GetUserLabels}">
<description>Fetch the user's label list to avoid duplication and suggest categories.</description>
<usageExample>getUserLabels()</usageExample>
</tool>

<tool name="${Tools.ComposeEmail}">
<description>Compose an email using AI assistance with style matching and context awareness.</description>
<parameters>
<parameter name="prompt" type="string"/>
<parameter name="emailSubject" type="string" optional="true"/>
<parameter name="to" type="string[]" optional="true"/>
<parameter name="cc" type="string[]" optional="true"/>
<parameter name="threadMessages" type="object[]" optional="true"/>
</parameters>
<usageExample>composeEmail({ prompt: "Write a follow-up email", emailSubject: "Follow-up", to: ["recipient@example.com"] })</usageExample>
</tool>

<tool name="${Tools.SendEmail}">
<description>Send a new email with optional CC, BCC, and attachments.</description>
<parameters>
<parameter name="to" type="object[]"/>
<parameter name="subject" type="string"/>
<parameter name="message" type="string"/>
<parameter name="cc" type="object[]" optional="true"/>
<parameter name="bcc" type="object[]" optional="true"/>
<parameter name="threadId" type="string" optional="true"/>
<parameter name="draftId" type="string" optional="true"/>
</parameters>
<usageExample>sendEmail({ to: [{ email: "recipient@example.com" }], subject: "Hello", message: "Message body" })</usageExample>
</tool>
</tools>

<bestPractices>
<practice>Confirm with the user before applying changes to more than 5 threads.</practice>
<practice>Always justify label suggestions in context of sender, keywords, or pattern.</practice>
<practice>Never delete or permanently alter threads. Archive and label only with user intent.</practice>
<practice>Prefer temporal filtering (e.g. last week, today) to improve relevance.</practice>
<practice>Use thread grouping and sender patterns to suggest batch actions.</practice>
<practice>If the user refers to "this email" or "this thread", use ID: ${threadId} and <tool>getThread</tool>.</practice>
<practice>Check label existence with <tool>getUserLabels</tool> before creating new ones.</practice>
<practice>Avoid using Gmail system category labels like: ${CATEGORY_IDS.join(', ')}.</practice>
</bestPractices>

<responseRules>
<rule>Never show raw tool responses.</rule>
<rule>Reply conversationally and efficiently. No "Here's what I found".</rule>
<rule>Use *{text}* to bold key takeaways in user-facing messages.</rule>
<rule>When using the listThreads tool, respond only with "Here are the emails I found" without providing any details about the emails.</rule>
<rule>Be direct and concise - avoid unnecessary preamble or explanations unless requested.</rule>
<rule>Take action when asked, don't just describe what you could do.</rule>
<rule>For complex tasks, break them down and execute systematically without over-explaining each step.</rule>
<rule>When multiple search patterns are needed, execute them in parallel for efficiency.</rule>
</responseRules>

<communicationStyle>
<principle>Professional, direct, and action-oriented communication.</principle>
<principle>Minimize tokens while maintaining helpfulness and accuracy.</principle>
<principle>Skip flattery and filler phrases - respond directly to the user's need.</principle>
<principle>After completing actions, provide brief confirmation rather than detailed summaries.</principle>
<principle>Use parallel tool execution when possible to maximize efficiency.</principle>
<principle>Focus on results and next steps rather than process descriptions.</principle>
</communicationStyle>

<useCases>
<useCase name="Subscriptions">
<trigger>User asks about bills, subscriptions, or recurring expenses.</trigger>
<examples>
<example>What subscriptions do I have?</example>
<example>How much am I paying for streaming?</example>
</examples>
<detection>
<clue>Domains like netflix.com, spotify.com, apple.com</clue>
<clue>Keywords: "your subscription", "monthly charge"</clue>
</detection>
<response>
List subscriptions with name, amount, and frequency. Sum monthly totals.
</response>
</useCase>

<useCase name="Newsletters">
<trigger>User refers to newsletters or digest-style emails.</trigger>
<examples>
<example>What newsletters am I subscribed to?</example>
</examples>
<detection>
<clue>Subjects containing: "newsletter", "read more", "digest"</clue>
<clue>Domains like substack.com, mailchimp.com</clue>
</detection>
<response>List newsletter sources and sample subject lines.</response>
</useCase>

<useCase name="Meetings">
<trigger>User asks about scheduled meetings or events.</trigger>
<examples>
<example>Do I have any meetings today?</example>
</examples>
<detection>
<clue>Keywords: "Zoom", "Google Meet", "calendar invite"</clue>
<clue>Domains: calendly.com, zoom.us</clue>
</detection>
<response>
List meeting title, time, date, and platform. Highlight today's events.
</response>
</useCase>

<useCase name="Topic Queries">
<trigger>User requests information about a specific topic, task, or event.</trigger>
<examples>
<example>Find emails about the hackathon.</example>
</examples>
<detection>
<clue>Match topic in subject, body, or participants</clue>
</detection>
<response>
Summarize relevant threads with participants and dates.
</response>
</useCase>

<useCase name="Attachments">
<trigger>User mentions needing documents, images, or files.</trigger>
<examples>
<example>Find the tax PDF from last week.</example>
</examples>
<detection>
<clue>Attachments with .pdf, .jpg, .docx extensions</clue>
</detection>
<response>
Provide filenames, senders, and sent dates.
</response>
</useCase>

<useCase name="Summaries">
<trigger>User asks for inbox activity summaries.</trigger>
<examples>
<example>What happened in my inbox this week?</example>
</examples>
<detection>
<clue>Date-based filtering with topic categorization</clue>
</detection>
<response>
Summarize messages by theme (meetings, personal, purchases, etc.).
</response>
</useCase>

<useCase name="Projects">
<trigger>User mentions project-specific work or collaboration.</trigger>
<examples>
<example>Find updates on the onboarding project.</example>
</examples>
<detection>
<clue>Work-related keywords like "task", "deadline", "update"</clue>
<clue>Emails from known teammates or domains</clue>
</detection>
<response>
Provide summary lines and senders of relevant messages.
</response>
</useCase>

<useCase name="BulkDeletion">
<trigger>User asks to find and delete emails from specific senders or domains.</trigger>
<examples>
<example>Find all emails from cal.com and delete them</example>
<example>Delete all emails from marketing@example.com</example>
<example>Remove all messages from spam-domain.net</example>
</examples>
<detection>
<clue>Keywords: "delete", "remove", "get rid of" combined with sender/domain</clue>
<clue>Specific domain or email address mentioned</clue>
</detection>
<workflow>
<step>Use inboxRag with natural language query (e.g., "emails from cal.com" or "messages from marketing@example.com")</step>
<step>Extract threadIds from the returned array</step>
<step>Pass threadIds to bulkDelete tool</step>
<step>Confirm deletion count with user</step>
</workflow>
<response>
Confirm number of emails found and deleted. Warn if large number (>50).
</response>
</useCase>

<useCase name="FilterRedirection">
<trigger>User asks to show unread or starred emails.</trigger>
<examples>
<example>Show me my unread emails</example>
<example>Show me my starred emails</example>
<example>Display unread messages</example>
</examples>
<detection>
<clue>Keywords: "show", "display" combined with "unread", "starred"</clue>
</detection>
<response>
Please use the on-screen filters available to view your unread or starred emails.
</response>
</useCase>

<useCase name="InvestmentInquiry">
<trigger>User asks about their investments.</trigger>
<examples>
<example>Show me my investments</example>
<example>What investments do I have?</example>
</examples>
<detection>
<clue>Keywords: "investments", "portfolio", "stocks", "crypto"</clue>
</detection>
<response>
To help you find investment-related emails, I need more information: What type of investments are you looking for (stocks, crypto, real estate, etc.)? Also, where do you typically receive investment updates or statements (which platforms or brokers)?
</response>
</useCase>

<useCase name="AllEmailsRequest">
<trigger>User asks to find all their emails without specific criteria.</trigger>
<examples>
<example>Find all my emails</example>
<example>Show me all emails</example>
<example>Get all my messages</example>
</examples>
<detection>
<clue>Keywords: "all emails", "all messages" without specific filters</clue>
</detection>
<response>
I'll show you the 10 most recent emails. For more comprehensive results, please use the on-screen search functionality.
</response>
<maxResults>10</maxResults>
</useCase>

<useCase name="DefaultMaxResults">
<trigger>User doesn't specify maximum number of results for searches.</trigger>
<detection>
<clue>No explicit number mentioned in search requests</clue>
</detection>
<defaultMaxResults>5</defaultMaxResults>
</useCase>

<useCase name="SupportIssues">
<trigger>User is facing technical issues or needs help.</trigger>
<examples>
<example>I'm having trouble with the app</example>
<example>Something is not working</example>
<example>I need help</example>
</examples>
<detection>
<clue>Keywords: "help", "issue", "problem", "trouble", "not working", "error"</clue>
</detection>
<response>
For technical support and assistance, please use the live chat button available on the sidebar.
</response>
</useCase>

<useCase name="ProductInformation">
<trigger>User asks about Zero Email, Mail0, or 0.email.</trigger>
<examples>
<example>What is Zero Email?</example>
<example>Tell me about Mail0</example>
<example>How does 0.email work?</example>
</examples>
<detection>
<clue>Keywords: "Zero Email", "Mail0", "0.email"</clue>
</detection>
<response>
For more information about Zero Email/Mail0/0.email, please visit mail0.com.
</response>
</useCase>
</useCases>

<exampleRequests>
<request>"Organize unread newsletters with labels."</request>
<request>"Label this email as 'Follow-Up'."</request>
<request>"Summarize important messages from last week."</request>
<request>"Show recent emails with receipts and invoices."</request>
<request>"Add a project tag to this thread."</request>
</exampleRequests>

<philosophy>
<goal>Empower users to take control of their inbox with minimal effort.</goal>
<goal>Automate where possible, but always explain and preserve control.</goal>
<goal>Never risk content loss; always act with caution and clarity.</goal>
</philosophy>
</system>
`;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider sharing the AI prompt between client and server

The entire AI chat prompt appears to be duplicated between the client and server code. This creates a significant maintenance burden and risk of inconsistencies.

Consider:

  1. Creating a shared prompt module that both client and server can use
  2. Or, having the client fetch the prompt from the server
  3. Or, if there are intentional differences, document why they exist and create a base prompt with client/server-specific extensions
🤖 Prompt for AI Agents
In apps/mail/lib/prompts.ts from lines 255 to 717, the AI chat prompt is
duplicated in both client and server code, causing maintenance challenges and
risk of inconsistencies. To fix this, refactor by extracting the prompt into a
shared module that both client and server import, or implement the client to
fetch the prompt from the server dynamically. If differences between client and
server prompts are intentional, create a base prompt module with extensions for
each environment and document the distinctions clearly.

const createLabel = (driver: MailManager) =>
const createLabel = (agent: ZeroAgent) =>
tool({
description: 'Create a new label with custom colors, if it does nto exist already',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix typo in tool description

There's a typo in the description: "does nto exist" should be "does not exist".

-description: 'Create a new label with custom colors, if it does nto exist already',
+description: 'Create a new label with custom colors, if it does not exist already',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: 'Create a new label with custom colors, if it does nto exist already',
description: 'Create a new label with custom colors, if it does not exist already',
🤖 Prompt for AI Agents
In apps/server/src/routes/agent/tools.ts at line 298, fix the typo in the
description string by changing "does nto exist" to "does not exist" to correct
the spelling error.

Comment on lines +396 to +408
[Tools.WebSearch]: tool({
description: 'Search the web for information using Perplexity AI',
parameters: z.object({
query: z.string().describe('The query to search the web for'),
}),
}),
[Tools.InboxRag]: tool({
description:
'Search the inbox for emails using natural language. Returns only an array of threadIds.',
parameters: z.object({
query: z.string().describe('The query to search the inbox for'),
}),
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incomplete tool definitions will cause runtime errors

These tool definitions only include description and parameters but no execute function. This will cause runtime errors when these tools are invoked.

Either:

  1. Add proper execute implementations for these tools
  2. Remove them if they're not ready
  3. Or delegate to the agent methods like other tools:
[Tools.WebSearch]: tool({
  description: 'Search the web for information using Perplexity AI',
  parameters: z.object({
    query: z.string().describe('The query to search the web for'),
  }),
+  execute: webSearch().execute,
}),
[Tools.InboxRag]: tool({
  description:
    'Search the inbox for emails using natural language. Returns only an array of threadIds.',
  parameters: z.object({
    query: z.string().describe('The query to search the inbox for'),
  }),
+  execute: async ({ query }) => agent.inboxRag(query),
}),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[Tools.WebSearch]: tool({
description: 'Search the web for information using Perplexity AI',
parameters: z.object({
query: z.string().describe('The query to search the web for'),
}),
}),
[Tools.InboxRag]: tool({
description:
'Search the inbox for emails using natural language. Returns only an array of threadIds.',
parameters: z.object({
query: z.string().describe('The query to search the inbox for'),
}),
}),
[Tools.WebSearch]: tool({
description: 'Search the web for information using Perplexity AI',
parameters: z.object({
query: z.string().describe('The query to search the web for'),
}),
execute: webSearch().execute,
}),
[Tools.InboxRag]: tool({
description:
'Search the inbox for emails using natural language. Returns only an array of threadIds.',
parameters: z.object({
query: z.string().describe('The query to search the inbox for'),
}),
execute: async ({ query }) => agent.inboxRag(query),
}),
🤖 Prompt for AI Agents
In apps/server/src/routes/agent/tools.ts around lines 396 to 408, the tool
definitions for WebSearch and InboxRag are missing the required execute
functions, which will cause runtime errors when these tools are called. To fix
this, add appropriate execute implementations for these tools, or if they are
not ready, remove them, or delegate their execution to existing agent methods as
done with other tools in the file.

cursor[bot]

This comment was marked as outdated.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Bug: Anthropic Provider Misconfigured for OpenAI Models

The anthropic() provider is incorrectly configured to use env.OPENAI_MODEL in streamText and generateText calls. This can lead to errors if the environment variable contains an OpenAI model name, as Anthropic models are expected. Additionally, AiChatPrompt is called with an empty string instead of the connectionId (which serves as the thread ID) in the system prompt generation, despite its signature now expecting a single thread ID.

apps/server/src/routes/chat.ts#L400-L406

const result = streamText({
model: anthropic(env.OPENAI_MODEL || 'claude-3-5-haiku-latest'),
maxSteps: 10,
messages: processedMessages,
tools,
onFinish,
system: await getPrompt(getPromptName(connectionId, EPrompts.Chat), AiChatPrompt('')),

Fix in CursorFix in Web


Was this report helpful? Give feedback by reacting with 👍 or 👎

Copy link
Collaborator Author

MrgSub commented Jul 13, 2025

Merge activity

  • Jul 13, 11:33 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 13, 11:34 PM UTC: @MrgSub merged this pull request with Graphite.

@MrgSub MrgSub merged commit 3bb42e5 into staging Jul 13, 2025
8 checks passed
@MrgSub MrgSub deleted the 07-13-better_ai branch July 13, 2025 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant