Conversation
WalkthroughThis set of changes introduces a new AI-powered thread search tool ( Changes
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
Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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)
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
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:
- Implementation of RAG (Retrieval Augmented Generation) for natural language email search
- Breaking down the monolithic
ToolResponsecomponent into specialized subcomponents - Increasing agent max steps from 5 to 10 to support more complex operations
- Optimizing memory usage in Cloudflare Workers by using a tag-based approach instead of returning full thread data
- 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
- While the changes are well-structured, the complexity of AI integration requires careful monitoring
- The score reflects concerns about the switch to Claude 3.5 and potential performance implications of parallel search operations
- 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 }) => { |
There was a problem hiding this comment.
style: toolCallId not used in InboxRag but used in WebSearch - inconsistent pattern
| return agent.listDrafts({ q, maxResults, pageToken }) as Awaited< | ||
| ReturnType<MailManager['listDrafts']> |
There was a problem hiding this comment.
style: This still uses Awaited while getDraft doesn't - consider standardizing the approach across both endpoints
| // 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', | ||
| // }, | ||
| // ], | ||
| // }; | ||
| // }, | ||
| // }), |
There was a problem hiding this comment.
style: remove commented code block and document changes in version control instead
| <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> |
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
syntax: typo in description: 'nto' should be 'not'
| 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', |
| [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'), | ||
| }), | ||
| }), |
There was a problem hiding this comment.
logic: WebSearch tool defined twice - this implementation has no execute function while the one above does. Remove duplicate definition.
| <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> |
There was a problem hiding this comment.
style: New response rules improve efficiency but rule about listThreads tool is now obsolete since tool was removed
| <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> |
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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.
| [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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Actionable comments posted: 5
🔭 Outside diff range comments (5)
apps/mail/components/mail/mail-display.tsx (1)
1643-1670: Separator condition uses the wrongindex– attachments get the outer e-mail index instead of their own
InsideemailData.attachments.map(...)the callback only receivesattachment, soindexresolves 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 deprecatedlistThreadstoolThis line references the
listThreadstool 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 ThreadPreviewThe 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 constantsThe 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 toolsThe
sendEmailtool 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 deprecatedlistThreadstoolSame issue as in the server-side prompt - this line references the removed
listThreadstool.
🧹 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 isattachmentId-filename, while the inline attachment list usesfilename-attachmentId. Also, if either property can beundefined, 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
ListThreadstool has been deprecated in favor ofInboxRag. 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 encodingThe HTML entities
<and>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 <thread id="{id}"/>. - 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 usageThe 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 codeLarge 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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: TurningDROP_AGENT_TABLESoff will persist local DB state – double-check dev/test workflowsChanging 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
maxtomaxResultsaligns 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
InboxRagenum 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
ListThreadswas removed and replaced byInboxRag, butListThreadsis 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
InboxRagto 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
ListThreadswas 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
InboxRagfunctionality.apps/server/src/trpc/routes/mail.ts (1)
62-95: LGTM! Consistent parameter naming applied.The renaming of
maxtomaxResultsimproves 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
Awaitedwrapper is appropriate sincegetDraftalready returns a Promise. This simplifies the type assertion without changing behavior.
23-31: Parameter naming aligned with other routes.The
max→maxResultsrename maintains consistency with the changes inmail.ts.apps/server/src/routes/agent/orchestrator.ts (2)
14-19: Connection context properly integrated.The addition of
connectionIdto the orchestrator enables proper agent instantiation for the newInboxRagtool. Good implementation.
70-83: InboxRag tool implementation looks good.The new streaming tool correctly:
- Uses the stored
connectionIdto get the agent- Calls
searchThreadswith appropriate parameters- Returns only the
threadIdsarray as specified in the descriptionapps/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
searchThreadsmethod 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
max→maxResultsrename 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 extractionThe 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.
| "AUTORAG_ID": "", | ||
| }, |
There was a problem hiding this comment.
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.
| "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.
| 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 <thread id="{id}"/>. | ||
| 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> | ||
| `; |
There was a problem hiding this comment.
🛠️ 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:
- Creating a shared prompt module that both client and server can use
- Or, having the client fetch the prompt from the server
- 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', |
There was a problem hiding this comment.
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.
| 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.
| [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'), | ||
| }), | ||
| }), |
There was a problem hiding this comment.
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:
- Add proper
executeimplementations for these tools - Remove them if they're not ready
- 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.
| [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.
There was a problem hiding this comment.
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
Zero/apps/server/src/routes/chat.ts
Lines 400 to 406 in a139873
Was this report helpful? Give feedback by reacting with 👍 or 👎

Description
Refactored the AI chat component and email assistant to improve performance, reliability, and user experience. This PR introduces a new
inboxRagtool for natural language email search, replaces the previouslistThreadsimplementation, and enhances the prompt design for more efficient inbox management.Type of Change
Areas Affected
Testing Done
Checklist
Additional Notes
Key improvements:
ThreadPreviewcomponent to replace the previous thread rendering approachToolResponseto use specialized subcomponents for different tool typesSummary by CodeRabbit
New Features
Improvements
Bug Fixes
Chores