feat: centralized LLM operation registry with tier-based model selection#1160
feat: centralized LLM operation registry with tier-based model selection#1160rsnodgrass wants to merge 2 commits intoelie222:mainfrom
Conversation
|
@rsnodgrass is attempting to deploy a commit to the Inbox Zero OSS Program Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughCentralizes LLM model selection by adding an operation-scoped registry and resolver (LLM_OPERATIONS, resolveModelTier, getModelForOperation), env-driven per-operation overrides, updates chatCompletionStream to accept operation IDs, and migrates many AI utilities to use operation-based model resolution. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as AI utility
participant Resolver as getModelForOperation
participant Registry as LLM_OPERATIONS
participant Env as env.LLM_OPERATION_OVERRIDES
participant Provider as getModel
Note over Caller,Resolver: Resolve model for an operation
Caller->>Resolver: getModelForOperation(userAi, operationId, online?)
Resolver->>Env: read LLM_OPERATION_OVERRIDES
Env-->>Resolver: optional overrides JSON
Resolver->>Registry: lookup operationId.defaultTier
Registry-->>Resolver: defaultTier
Resolver->>Resolver: resolveTier(prefer env override else default)
Resolver->>Provider: getModel(userAi, mappedModelType, online?)
Provider-->>Resolver: SelectModel (provider, model, providerOptions)
Resolver-->>Caller: SelectModel
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ 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. Comment |
709620e to
6b4230f
Compare
Introduce an operation-scoped LLM registry and update web AI flows to select models via
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (7)
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (1)
149-163: Consider using the importedisConversationStatusTypehelper for consistency.The
isConversationStatusTypefunction is imported but not used in this memo. Using it would ensure consistent logic across the codebase and handle the null check properly.🔎 Suggested refactor
const isReplyZeroDisabled = useMemo(() => { if (!isGoogleProvider(provider)) return false; const conversationStatusRules = (data || []).filter( - (rule) => - rule.systemType && CONVERSATION_STATUS_TYPES.includes(rule.systemType), + (rule) => isConversationStatusType(rule.systemType), ); // If no conversation status rules exist, Reply Zero is not enabled if (conversationStatusRules.length === 0) return true; // If all conversation status rules are disabled, Reply Zero is effectively disabled return conversationStatusRules.every((rule) => !rule.enabled); }, [data, provider]);apps/web/app/(app)/[emailAccountId]/settings/ReplyZeroSection.tsx (1)
25-46: Consider handling partial failures more explicitly.If some rule toggles succeed while others fail, the current implementation shows a generic error toast but still calls
mutate()on success. In a partial failure scenario (e.g., 3 of 4 rules toggle successfully), the user sees an error but the actual state becomes inconsistent.🔎 Suggested improvement
const hasError = results.some((result) => result?.serverError); if (hasError) { + // Refresh to show actual server state after partial failure + mutate(); toast.error( `Error ${newEnabled ? "enabling" : "disabling"} Reply Zero`, ); } else { mutate(); toast.success( `Reply Zero ${newEnabled ? "enabled" : "disabled"} successfully`, ); }apps/web/utils/llms/CLAUDE.md (1)
56-68: Optional: Add language specifier to code fence.The decision tree code block is missing a language specifier. Consider adding
textor leaving it as plain markdown for better rendering consistency.🔎 Suggested improvement
-``` +```text Is it per-email? → economy (volume too high for expensive models)apps/web/utils/ai/report/analyze-email-behavior.ts (1)
6-8: LGTM with a note on the unused logger.The migration to
getModelForOperationwith operation ID"report.analyze-behavior"is correct. The logger is renamed to_loggerindicating it's intentionally unused, but the logger import and instantiation could be removed entirely if logging is not planned for this function.🔎 Optional: Remove unused logger
If logging is not needed in this function, consider removing the unused logger:
import { z } from "zod"; import { createGenerateObject } from "@/utils/llms"; import type { EmailAccountWithAI } from "@/utils/llms/types"; import type { EmailSummary } from "@/utils/ai/report/summarize-emails"; -import { createScopedLogger } from "@/utils/logger"; import { getModelForOperation } from "@/utils/llms/resolve-model"; -const _logger = createScopedLogger("email-report-email-behavior"); - const emailBehaviorSchema = z.object({apps/web/utils/ai/report/analyze-label-optimization.ts (1)
9-9: Remove unused logger instance.The
_loggerinstance is created but never used in this file. If logging is needed in the future, consider passing a logger as a parameter toaiAnalyzeLabelOptimizationrather than maintaining a file-level logger instance.🔎 Proposed fix
-const _logger = createScopedLogger("email-report-label-analysis"); - const labelAnalysisSchema = z.object({Based on coding guidelines: "Logger should be passed as a parameter to helper functions instead of creating their own logger instances."
apps/web/env.ts (1)
46-57: JSON parsing could benefit from error logging.The implementation correctly handles JSON parsing failures by returning
undefined, but silently swallowing parse errors could make debugging difficult. Consider logging parse failures to help operators identify malformed JSON configurations.🔎 Proposed enhancement
LLM_OPERATION_OVERRIDES: z .string() .optional() .transform((value) => { if (!value) return undefined; try { return JSON.parse(value) as Record<string, string>; - } catch { + } catch (error) { + console.error('Failed to parse LLM_OPERATION_OVERRIDES:', error); return undefined; } }),apps/web/utils/ai/report/generate-actionable-recommendations.ts (1)
9-9: Remove unused logger variable.The
_loggervariable is created but never used in this file. Either remove it entirely or use it for logging within the function.🔎 Proposed fix
-const _logger = createScopedLogger("email-report-actionable-recommendations"); - const actionableRecommendationsSchema = z.object({
There was a problem hiding this comment.
2 issues found across 52 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx">
<violation number="1" location="apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx:150">
P2: Warning banner will flash incorrectly during loading state. When `data` is undefined, the empty array fallback causes `isReplyZeroDisabled` to return `true` for Google providers before the actual rules are fetched. Consider checking if data exists before determining the disabled state.</violation>
</file>
<file name="apps/web/env.ts">
<violation number="1" location="apps/web/env.ts:54">
P2: Silently ignoring invalid JSON in `LLM_OPERATION_OVERRIDES` could lead to undetected misconfiguration. Operators won't know their overrides aren't being applied if they have a JSON syntax error. Consider logging a warning when parsing fails so issues are discoverable.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
6b4230f to
77be505
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (5)
apps/web/utils/llms/CLAUDE.md (1)
1-97: Excellent documentation! Fix minor markdown formatting issues.This guide provides clear, actionable guidance for model selection. The tier-based approach and decision tree are particularly helpful.
Minor markdown formatting fixes
Fix the following formatting issues flagged by linters:
- Lines 20, 27: Add blank lines before and after tables
- Line 56: Add language identifier to code block
## Recommended Models (January 2026) + ### Anthropic + | Tier | Model | Pricing ($/M tokens) |## Quick Decision Tree -``` +```text Is it per-email?apps/web/utils/ai/report/analyze-label-optimization.ts (1)
9-9: Unused logger variable.The logger is created but never used in this function. Per coding guidelines, LLM feature functions should "log inputs and outputs with appropriate log levels." Consider either using the logger to trace inputs/outputs or removing it if logging is intentionally handled elsewhere.
Option 1: Use the logger
-const _logger = createScopedLogger("email-report-label-analysis"); +const logger = createScopedLogger("email-report-label-analysis");Then add logging in the function:
logger.info("Analyzing label optimization", { emailCount: emailSummaries.length, labelCount: gmailLabels.length, });Option 2: Remove unused logger
-import { createScopedLogger } from "@/utils/logger"; - -const _logger = createScopedLogger("email-report-label-analysis");apps/web/utils/ai/reply/generate-nudge.ts (1)
10-14: UnusedonFinishparameter.The
onFinishcallback is declared in the function signature but never used. This could be dead code from a previous implementation.If not needed, remove the parameter
export async function aiGenerateNudge({ messages, emailAccount, }: { messages: EmailForLLM[]; emailAccount: EmailAccountWithAI; - onFinish?: (completion: string) => Promise<void>; }) {apps/web/utils/ai/report/generate-executive-summary.ts (1)
9-9: Unused logger variable.Similar to
analyze-label-optimization.ts, the logger is created but never used. Consider either using it for tracing or removing it.apps/web/utils/llms/resolve-model.ts (1)
47-49: Consider deriving valid tiers from the type or constant.The hardcoded array
["reasoning", "fast", "economy"]could become out of sync with theModelTiertype. Consider deriving the valid values fromTIER_TO_MODEL_TYPEkeys instead:🔎 Proposed refactor
function isValidTier(value: string): value is ModelTier { - return ["reasoning", "fast", "economy"].includes(value); + return (Object.keys(TIER_TO_MODEL_TYPE) as string[]).includes(value); }This approach ensures the validation logic stays in sync with the tier mapping automatically.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (47)
apps/web/.env.exampleapps/web/env.tsapps/web/utils/ai/assistant/chat.tsapps/web/utils/ai/assistant/process-user-request.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/categorize-sender/ai-categorize-senders.tsapps/web/utils/ai/categorize-sender/ai-categorize-single-sender.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/utils/ai/choose-rule/ai-detect-recurring-pattern.tsapps/web/utils/ai/choose-rule/choose-args.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/digest/summarize-email-for-digest.tsapps/web/utils/ai/group/create-group.tsapps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/ai/knowledge/extract.tsapps/web/utils/ai/knowledge/persona.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/mcp/mcp-agent.tsapps/web/utils/ai/meeting-briefs/generate-briefing.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/reply/check-if-needs-reply.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/report/analyze-email-behavior.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/report/build-user-persona.tsapps/web/utils/ai/report/generate-actionable-recommendations.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/report/response-patterns.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/diff-rules.tsapps/web/utils/ai/rule/find-existing-rules.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/snippets/find-snippets.tsapps/web/utils/cold-email/is-cold-email.tsapps/web/utils/llms/CLAUDE.mdapps/web/utils/llms/index.tsapps/web/utils/llms/operations.test.tsapps/web/utils/llms/operations.tsapps/web/utils/llms/resolve-model.tsturbo.json
💤 Files with no reviewable changes (2)
- apps/web/utils/ai/choose-rule/choose-args.ts
- apps/web/utils/ai/choose-rule/run-rules.ts
🚧 Files skipped from review as they are similar to previous changes (24)
- apps/web/utils/llms/operations.test.ts
- turbo.json
- apps/web/utils/ai/assistant/process-user-request.ts
- apps/web/utils/ai/knowledge/extract.ts
- apps/web/utils/ai/categorize-sender/ai-categorize-single-sender.ts
- apps/web/utils/ai/group/create-group.ts
- apps/web/utils/ai/snippets/find-snippets.ts
- apps/web/utils/ai/digest/summarize-email-for-digest.ts
- apps/web/utils/ai/report/analyze-email-behavior.ts
- apps/web/utils/ai/mcp/mcp-agent.ts
- apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
- apps/web/utils/ai/knowledge/persona.ts
- apps/web/.env.example
- apps/web/utils/cold-email/is-cold-email.ts
- apps/web/utils/llms/index.ts
- apps/web/utils/ai/report/build-user-persona.ts
- apps/web/utils/ai/rule/diff-rules.ts
- apps/web/utils/ai/rule/find-existing-rules.ts
- apps/web/utils/ai/reply/check-if-needs-reply.ts
- apps/web/utils/ai/assistant/chat.ts
- apps/web/utils/ai/choose-rule/ai-detect-recurring-pattern.ts
- apps/web/utils/ai/report/generate-actionable-recommendations.ts
- apps/web/utils/ai/report/response-patterns.ts
- apps/web/utils/ai/meeting-briefs/generate-briefing.ts
🧰 Additional context used
📓 Path-based instructions (17)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
Don't export imported variables
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
apps/web/{utils/ai,utils/llms,__tests__}/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)
LLM-related code must be organized in specific directories:
apps/web/utils/ai/for main implementations,apps/web/utils/llms/for core utilities and configurations, andapps/web/__tests__/for LLM-specific tests
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
apps/web/utils/ai/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)
apps/web/utils/ai/**/*.ts: LLM feature functions must import fromzodfor schema validation, usecreateScopedLoggerfrom@/utils/logger,chatCompletionObjectandcreateGenerateObjectfrom@/utils/llms, and importEmailAccountWithAItype from@/utils/llms/types
LLM feature functions must follow a standard structure: accept options withinputDataandemailAccountparameters, implement input validation with early returns, define separate system and user prompts, create a Zod schema for response validation, and usecreateGenerateObjectto execute the LLM call
System prompts must define the LLM's role and task specifications
User prompts must contain the actual data and context, and should be kept separate from system prompts
Always define a Zod schema for LLM response validation and make schemas as specific as possible to guide the LLM output
Use descriptive scoped loggers for each LLM feature, log inputs and outputs with appropriate log levels, and include relevant context in log messages
Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures usingwithRetry
Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions
Use TypeScript types for all LLM function parameters and return values, and define clear interfaces for complex input/output structures
Keep related AI functions in the same file or directory, extract common patterns into utility functions, and document complex AI logic with clear comments
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma'sselectoption. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. AllfindUnique/findFirstcalls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
AllfindManyqueries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g.,emailAccount: { id: emailAccountId }) to validate ownership
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Make sure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include atitleelement for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
AssigntabIndexto non-interactive HTML elements witharia-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
Make sure all anchors are valid and navigable
Ensure all ARIA properties (aria-*) are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid AR...
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/llms/CLAUDE.mdapps/web/utils/ai/choose-rule/ai-choose-rule.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
Logger should be passed as a parameter to helper functions instead of creating their own logger instances
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Format code with Prettier
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
apps/web/**/*.{example,ts,json}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Add environment variables to
.env.example,env.ts, andturbo.json
Files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
apps/web/env.ts
📄 CodeRabbit inference engine (.cursor/rules/environment-variables.mdc)
apps/web/env.ts: Add server-only environment variables toapps/web/env.tsunder theserverobject with Zod schema validation
Add client-side environment variables toapps/web/env.tsunder theclientobject withNEXT_PUBLIC_prefix and Zod schema validation
Add client-side environment variables toapps/web/env.tsunder theexperimental__runtimeEnvobject to enable runtime access
Files:
apps/web/env.ts
{.env.example,apps/web/env.ts}
📄 CodeRabbit inference engine (.cursor/rules/environment-variables.mdc)
Client-side environment variables must be prefixed with
NEXT_PUBLIC_
Files:
apps/web/env.ts
🧠 Learnings (35)
📓 Common learnings
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use TypeScript types for all LLM function parameters and return values, and define clear interfaces for complex input/output structures
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/llms/CLAUDE.mdapps/web/utils/ai/choose-rule/ai-choose-rule.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must follow a standard structure: accept options with `inputData` and `emailAccount` parameters, implement input validation with early returns, define separate system and user prompts, create a Zod schema for response validation, and use `createGenerateObject` to execute the LLM call
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/env.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/llms/CLAUDE.mdapps/web/utils/ai/choose-rule/ai-choose-rule.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/reply/reply-context-collector.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/reply/reply-context-collector.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/ai/clean/ai-clean.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/report/generate-executive-summary.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : System prompts must define the LLM's role and task specifications
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/llms/{index,model}.ts : Core LLM functionality must be defined in `utils/llms/index.ts`, model definitions and configurations in `utils/llms/model.ts`, and usage tracking in `utils/usage.ts`
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/llms/CLAUDE.md
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use TypeScript types for all LLM function parameters and return values, and define clear interfaces for complex input/output structures
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/llms/resolve-model.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/env.tsapps/web/utils/ai/meeting-briefs/research-guest.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Always define a Zod schema for LLM response validation and make schemas as specific as possible to guide the LLM output
Applied to files:
apps/web/utils/ai/knowledge/extract-from-email-history.tsapps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/utils/ai/clean/ai-clean-select-labels.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/{utils/ai,utils/llms,__tests__}/**/*.ts : LLM-related code must be organized in specific directories: `apps/web/utils/ai/` for main implementations, `apps/web/utils/llms/` for core utilities and configurations, and `apps/web/__tests__/` for LLM-specific tests
Applied to files:
apps/web/utils/llms/resolve-model.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/calendar/availability.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`
Applied to files:
apps/web/utils/llms/resolve-model.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/env.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use descriptive scoped loggers for each LLM feature, log inputs and outputs with appropriate log levels, and include relevant context in log messages
Applied to files:
apps/web/utils/llms/resolve-model.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/env.tsapps/web/utils/llms/operations.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/ai/report/generate-executive-summary.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Keep related AI functions in the same file or directory, extract common patterns into utility functions, and document complex AI logic with clear comments
Applied to files:
apps/web/utils/llms/resolve-model.tsapps/web/utils/ai/report/analyze-label-optimization.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/ai/clean/ai-clean.tsapps/web/utils/ai/calendar/availability.tsapps/web/utils/llms/CLAUDE.mdapps/web/utils/ai/choose-rule/ai-choose-rule.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions
Applied to files:
apps/web/utils/ai/reply/generate-nudge.tsapps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/knowledge/writing-style.tsapps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/clean/ai-clean-select-labels.tsapps/web/utils/ai/clean/ai-clean.ts
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Use test helpers `getEmail`, `getEmailAccount`, and `getRule` from `@/__tests__/helpers` for mocking emails, accounts, and rules
Applied to files:
apps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Prefer using existing helpers from `@/__tests__/helpers.ts` (`getEmailAccount`, `getEmail`, `getRule`, `getMockMessage`, `getMockExecutedRule`) instead of creating custom test data helpers
Applied to files:
apps/web/utils/ai/reply/draft-with-knowledge.tsapps/web/utils/ai/report/summarize-emails.tsapps/web/utils/ai/choose-rule/ai-choose-args.tsapps/web/utils/ai/reply/reply-context-collector.tsapps/web/utils/ai/choose-rule/ai-choose-rule.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `actionClient` when both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client
Applied to files:
apps/web/utils/ai/reply/draft-with-knowledge.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : User prompts must contain the actual data and context, and should be kept separate from system prompts
Applied to files:
apps/web/utils/ai/rule/prompt-to-rules.tsapps/web/utils/ai/rule/generate-rules-prompt.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Export types from Zod schemas using `z.infer<>` to maintain type safety between validation and client usage
Applied to files:
apps/web/utils/ai/rule/generate-rules-prompt.tsapps/web/utils/ai/clean/ai-clean-select-labels.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add server-only environment variables to `apps/web/env.ts` under the `server` object with Zod schema validation
Applied to files:
apps/web/env.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : Define environment variables in `apps/web/env.ts` using Zod schema validation, organizing them into `server` and `client` sections
Applied to files:
apps/web/env.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add client-side environment variables to `apps/web/env.ts` under the `experimental__runtimeEnv` object to enable runtime access
Applied to files:
apps/web/env.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add client-side environment variables to `apps/web/env.ts` under the `client` object with `NEXT_PUBLIC_` prefix and Zod schema validation
Applied to files:
apps/web/env.ts
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/**/*.{example,ts,json} : Add environment variables to `.env.example`, `env.ts`, and `turbo.json`
Applied to files:
apps/web/env.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : For client-side environment variables in `apps/web/env.ts`, prefix them with `NEXT_PUBLIC_` and add them to both the `client` and `experimental__runtimeEnv` sections
Applied to files:
apps/web/env.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Define Zod validation schemas in separate `*.validation.ts` files and export both the schema and inferred type (e.g., `CreateExampleBody`)
Applied to files:
apps/web/utils/ai/clean/ai-clean-select-labels.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Define input validation schemas using Zod in `.validation.ts` files and export both the schema and its inferred TypeScript type
Applied to files:
apps/web/utils/ai/clean/ai-clean-select-labels.ts
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/utils/actions/**/*.{ts,tsx} : Infer types from Zod schemas using `z.infer<typeof schema>` instead of duplicating as separate interfaces
Applied to files:
apps/web/utils/ai/clean/ai-clean-select-labels.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `.schema()` method with Zod validation schemas from corresponding `.validation.ts` files in next-safe-action configuration
Applied to files:
apps/web/utils/ai/clean/ai-clean-select-labels.ts
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*.validation.ts : Define validation schemas using Zod
Applied to files:
apps/web/utils/ai/clean/ai-clean-select-labels.ts
📚 Learning: 2025-11-25T14:42:11.919Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-11-25T14:42:11.919Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use lodash utilities for common operations (arrays, objects, strings)
Applied to files:
apps/web/utils/ai/clean/ai-clean.ts
📚 Learning: 2025-12-31T23:49:06.009Z
Learnt from: rsnodgrass
Repo: elie222/inbox-zero PR: 1154
File: apps/web/app/api/user/setup-progress/route.ts:0-0
Timestamp: 2025-12-31T23:49:06.009Z
Learning: In apps/web/app/api/user/setup-progress/route.ts, Reply Zero enabled status should be determined solely by checking if the TO_REPLY rule is enabled, as it is the critical/canonical rule that Reply Zero is based on. The other conversation status types (FYI, AWAITING_REPLY, ACTIONED) should not be checked for determining Reply Zero setup progress.
<!-- </add_learning>
Applied to files:
apps/web/utils/ai/reply/determine-thread-status.ts
🧬 Code graph analysis (16)
apps/web/utils/ai/knowledge/extract-from-email-history.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/reply/generate-nudge.ts (2)
apps/web/utils/ai/helpers.ts (1)
getTodayForLLM(5-7)apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/reply/draft-with-knowledge.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/report/analyze-label-optimization.ts (2)
apps/web/utils/logger.ts (1)
createScopedLogger(18-82)apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/knowledge/writing-style.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/rule/prompt-to-rules.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/report/summarize-emails.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/rule/generate-rules-prompt.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/meeting-briefs/research-guest.ts (3)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)apps/web/env.ts (1)
env(17-273)apps/web/utils/llms/config.ts (1)
Provider(3-12)
apps/web/utils/ai/choose-rule/ai-choose-args.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/clean/ai-clean-select-labels.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/clean/ai-clean.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/reply/determine-thread-status.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/report/generate-executive-summary.ts (2)
apps/web/utils/logger.ts (1)
createScopedLogger(18-82)apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/reply/reply-context-collector.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (1)
apps/web/utils/llms/resolve-model.ts (1)
getModelForOperation(63-80)
🪛 LanguageTool
apps/web/utils/llms/CLAUDE.md
[style] ~13-~13: Consider a different adjective to strengthen your wording.
Context: ...oning** | DEFAULT_LLM_* | Best quality, deep understanding | | fast | CHAT_LLM_*...
(DEEP_PROFOUND)
[grammar] ~97-~97: Use a hyphen to join words.
Context: ...to upgrade specific operations to higher quality models without code changes.
(QB_NEW_EN_HYPHEN)
🪛 markdownlint-cli2 (0.18.1)
apps/web/utils/llms/CLAUDE.md
20-20: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
27-27: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
56-56: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/utils/ai/clean/ai-clean.ts (1)
44-54: Remove duplicate "Notifications" entry.The word "Notifications" appears twice in the list (lines 47 and 49). Remove one occurrence.
🔎 Proposed fix
Examples of emails to archive: - Newsletters - Marketing - Notifications - Low-priority emails -- Notifications - Social - LinkedIn messages - Facebook messages - GitHub issuesapps/web/utils/llms/index.ts (1)
221-245: Two call sites are missing the requiredoperationIdparameter.The function signature requires
operationId: LLMOperationId, but these files have not been updated:
apps/web/app/api/ai/summarise/controller.ts: MissingoperationIdin thechatCompletionStreamcallapps/web/app/api/ai/compose-autocomplete/route.ts: MissingoperationIdin thechatCompletionStreamcallAdd an appropriate operation ID to each call (e.g.,
operationId: "assistant.chat"or other applicable IDs fromLLM_OPERATIONS).
♻️ Duplicate comments (4)
apps/web/utils/ai/report/summarize-emails.ts (1)
64-66: Inconsistent bullet point markers in system prompt.The bullet markers changed from
--to+-which appears unintentional and creates inconsistent formatting (line 67 still uses-). Please normalize to use single-for all bullet points.apps/web/utils/llms/operations.ts (3)
32-38: Inconsistency between PR description and code tier assignment.The PR summary states "rule.match-email" was moved to "economy" tier, but the code assigns
defaultTier: "reasoning"with rationale emphasizing high-consequence automation. Please align the code with the PR description or update the PR description to reflect the reasoning tier assignment.This issue was previously flagged in past review comments.
85-91: Inconsistency between PR description and code tier assignment.The PR summary lists "reply.determine-thread-status" as moving to "economy" tier, but the implementation uses
defaultTier: "reasoning"with rationale emphasizing the criticality of correct status determination for Reply Zero. Please reconcile this discrepancy by updating either the code or the PR description.This issue was previously flagged in past review comments.
158-164: Inconsistency between PR description and code tier assignment.The PR summary claims "clean.decide-archive" was moved to "economy" tier, but the code shows
defaultTier: "reasoning"with rationale emphasizing data loss risk. The strong justification for reasoning tier contradicts the PR's claimed cost savings. Please clarify whether this operation should remain at reasoning tier (and update PR docs) or move to economy tier (and update code + rationale).This issue was previously flagged in past review comments.
🧹 Nitpick comments (7)
apps/web/utils/ai/report/analyze-label-optimization.ts (1)
9-9: Clarify the unused logger variable.The logger is renamed to
_loggerbut doesn't appear to be used anywhere in this file. If it's intentionally unused, consider removing it entirely to reduce clutter. If it should be used for error logging, the_prefix convention typically indicates an intentionally unused parameter, which doesn't apply here.🔎 Proposed fix
-const _logger = createScopedLogger("email-report-label-analysis");apps/web/utils/ai/report/generate-executive-summary.ts (2)
9-9: Remove unused logger instead of prefixing with underscore.The logger is renamed to
_loggerbut is never used in this file. Per coding guidelines, unused imports should be removed entirely rather than prefixed with underscore.🔎 Suggested cleanup
-import { createScopedLogger } from "@/utils/logger"; - -const _logger = createScopedLogger("email-report-executive-summary");
64-64: Minor prompt text change: verify if intentional.The example persona changed from "Software Engineer" to "Software Developer". While both are valid, this change is unrelated to the main refactoring. If intentional for better LLM understanding, it's fine; otherwise, it could be reverted for consistency.
apps/web/utils/ai/report/generate-actionable-recommendations.ts (1)
9-9: Note: Unused logger variable.The logger is renamed to
_loggerwith an underscore prefix, which conventionally indicates an intentionally unused variable. If logging is not needed in this module, consider removing the import and variable declaration to reduce clutter.apps/web/utils/ai/report/analyze-email-behavior.ts (1)
8-8: Remove unused logger variable.The
_loggervariable is created but never used in this file. Consider removing it to eliminate dead code.🔎 Suggested change
-const _logger = createScopedLogger("email-report-email-behavior"); -apps/web/utils/llms/CLAUDE.md (1)
20-20: Fix markdown formatting issues.For better markdown rendering and consistency:
- Add blank lines before and after tables (lines 20, 27)
- Specify language for the fenced code block at line 56
🔎 Proposed fixes
For lines 19-21 (first table):
### Anthropic + | Tier | Model | Pricing ($/M tokens) | |------|-------|----------------------| +For lines 26-28 (second table):
### OpenAI + | Tier | Model | Pricing ($/M tokens) | |------|-------|----------------------| +For line 56 (code block):
-``` +```text Is it per-email?Also applies to: 27-27, 56-56
apps/web/utils/ai/report/response-patterns.ts (1)
8-8: Consider removing the unused logger.The logger is renamed with an underscore prefix (
_logger) but is never used in this file. Consider removing it entirely to reduce clutter.🔎 Proposed fix
-import { createScopedLogger } from "@/utils/logger"; import { getModelForOperation } from "@/utils/llms/resolve-model"; - -const _logger = createScopedLogger("email-report-response-patterns");
|
Hey @rsnodgrass! Thanks for another PR! Curious what the reasoning is behind the centralised registry? |
@elie222 This makes it easy for someone to see all the LLM calls in one place without having to dig around the codebase and make tuning adjustments (if they want, though this should be rare). It is similar to how a best practice for LLM agents is maintaining all prompts outside source code (like Markdown) so that reviewers/contributors to a system don't have to be engineers. Product developers can improve upon prompts since they are approachable. I started down this path of fine tuning which model was used because I noticed Inbox Zero was cranking through $ on my Anthropic account. Then I realized there was no clarity around which model was picked for different use cases, so added reasoning/docs for each chocie. I don't want to step on any toes, so feel free to push back on anything! Inbox Zero is great, thanks for building this. |
|
Separately from these improvements, I also realized that my using Anthropic is fairly expensive for Inbox Zero compared to OpenAI since Vercel's handling of AI mapping causes some big issues in context management especially around effective use of cached tokens. I'm switching to OpenAI and associated models since it would not be easy to fix this. |
Introduces a centralized LLM operation registry that maps each AI operation to a model tier (reasoning/fast/economy) with documented rationale. Key changes: - Created operations.ts with 34 LLM operations and tier assignments - Created resolve-model.ts with configuration resolver - Added LLM_OPERATION_OVERRIDES env var for per-operation tier overrides - Migrated all getModel() call sites to use getModelForOperation() - Added developer documentation in CLAUDE.md Tier optimization rationale: - per-email operations -> economy (high volume, cost sensitive) - interactive UI operations -> fast (latency matters) - user-visible/persisted outputs -> reasoning (quality matters) - rare setup actions -> reasoning (cost is negligible) Expected annual cost savings per user account: - Anthropic: ~$743/year (67% reduction) - OpenAI: ~$800/year (94% reduction)
77be505 to
96a2567
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/utils/llms/index.ts (1)
221-245: Breaking API change: Two callers still missing requiredoperationIdparameter.The
chatCompletionStreamsignature now requiresoperationId: LLMOperationId, but the following call sites have not been migrated:
apps/web/app/api/ai/summarise/controller.ts:21apps/web/app/api/ai/compose-autocomplete/route.ts:21These calls must include an operationId parameter. One caller (
apps/web/utils/ai/assistant/chat.ts:968) has been correctly migrated withoperationId: "assistant.chat".
♻️ Duplicate comments (2)
apps/web/utils/ai/report/summarize-emails.ts (1)
64-67: Bullet formatting appears consistent now.The bullet markers on lines 64-66 use
-prefix consistently with line 67. If a previous iteration had inconsistent markers (+-or--), it appears to be resolved in this version.apps/web/utils/llms/resolve-model.ts (1)
37-42: Duplicate concern: env.ts validation noted in past review.Line 40 assumes env.ts validates all values are valid
ModelTier, but a past review comment identified that the Zod schema uses a looseRecord<string, string>type and recommended strengthening validation during environment parsing to filter invalid tier values.
🧹 Nitpick comments (5)
apps/web/utils/ai/report/generate-actionable-recommendations.ts (1)
9-9: Unused logger — consider adding logging or removing it.The logger is created but never used (hence the
_prefix). Per coding guidelines, LLM feature functions should "log inputs and outputs with appropriate log levels." Consider adding logging for the LLM call or removing the unused declaration.Option 1: Add logging (recommended)
-const _logger = createScopedLogger("email-report-actionable-recommendations"); +const logger = createScopedLogger("email-report-actionable-recommendations"); export async function aiGenerateActionableRecommendations( emailSummaries: EmailSummary[], emailAccount: EmailAccountWithAI, userPersona: UserPersona, ): Promise<z.infer<typeof actionableRecommendationsSchema>> { + logger.info("Generating actionable recommendations", { + emailCount: emailSummaries.length, + persona: userPersona.professionalIdentity.persona, + });Option 2: Remove unused logger
-import { createScopedLogger } from "@/utils/logger"; - -const _logger = createScopedLogger("email-report-actionable-recommendations");Based on learnings, LLM feature functions should include descriptive scoped loggers with appropriate log levels.
apps/web/utils/llms/CLAUDE.md (1)
56-71: Consider adding a language identifier to the decision tree code block.The fenced code block for the decision tree lacks a language specifier. While this is a text diagram rather than code, adding a language identifier (e.g.,
```text) would satisfy linting rules and improve rendering consistency.🔎 Proposed fix
-``` +```text Is it choosing which rule to apply or drafting a reply? → reasoning (accuracy critical - wrong rule = wrong automation)apps/web/utils/ai/choose-rule/ai-choose-rule.ts (1)
175-188: Inconsistent return structure between single-rule and multi-rule functions.
getAiResponseSingleRulereturns{ result, modelOptions }(lines 177-187), whilegetAiResponseMultiRulereturns just the result object directly (lines 292-296). The caller at line 85-93 expects the multi-rule function to return only the result, which works, but this asymmetry could cause confusion.This appears to be pre-existing behavior and not introduced by this PR's changes, so it's not blocking.
Also applies to: 292-296
apps/web/utils/llms/resolve-model.ts (2)
25-35: Add defensive check for invalid operation IDs.If
operationIdis not present inLLM_OPERATIONS, Line 34 will throw a runtime error. Consider adding a guard or fallback:🔎 Proposed defensive check
export function resolveModelTier(operationId: LLMOperationId): ModelTier { const envOverride = getEnvOverride(operationId); if (envOverride) { logger.info("Using env override for operation", { operationId, tier: envOverride, }); return envOverride; } + const operation = LLM_OPERATIONS[operationId]; + if (!operation) { + logger.error("Unknown operation ID", { operationId }); + // Fallback to a safe default + return "economy"; + } - return LLM_OPERATIONS[operationId].defaultTier; + return operation.defaultTier; }
56-73: Add error handling for invalid operation lookups.Line 61 directly accesses
LLM_OPERATIONS[operationId]without validation. If the operation ID is invalid, this will throw a runtime error. Consider adding a defensive check similar to the one suggested forresolveModelTier.🔎 Proposed defensive check
export function getModelForOperation( userAi: UserAIFields, operationId: LLMOperationId, online = false, ): SelectModel { const operation = LLM_OPERATIONS[operationId]; + if (!operation) { + logger.error("Unknown operation ID, falling back to economy", { + operationId, + }); + return getModel(userAi, "economy", online); + } const tier = resolveModelTier(operationId); const modelType = TIER_TO_MODEL_TYPE[tier]; logger.info("Getting model for operation", { operationId, tier, modelType, frequency: operation.frequency, }); return getModel(userAi, modelType, online); }
| export async function aiDetermineThreadStatus({ | ||
| emailAccount, | ||
| threadMessages, | ||
| modelType, | ||
| userSentLastEmail = false, | ||
| }: { | ||
| emailAccount: EmailAccountWithAI; | ||
| threadMessages: EmailForLLM[]; | ||
| modelType?: ModelType; | ||
| userSentLastEmail?: boolean; | ||
| }): Promise<{ status: ConversationStatus; rationale: string }> { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Search for all calls to aiDetermineThreadStatus
rg -n "aiDetermineThreadStatus\(" --type ts -B 2 -A 5Repository: elie222/inbox-zero
Length of output: 12797
Remove modelType parameter from aiDetermineThreadStatus call in handle-conversation-status.ts.
The caller in apps/web/utils/reply-tracker/handle-conversation-status.ts (line 75) still passes modelType, but the function no longer accepts this parameter. Update the call to match the other callers:
Location to fix
apps/web/utils/reply-tracker/handle-conversation-status.ts:72-77
const { status, rationale } = await aiDetermineThreadStatus({
emailAccount,
threadMessages: threadMessagesForLLM,
modelType, // ← Remove this line
userSentLastEmail,
});
🤖 Prompt for AI Agents
In apps/web/utils/reply-tracker/handle-conversation-status.ts around lines 72 to
77, the call to aiDetermineThreadStatus still passes a now-removed modelType
parameter; remove the modelType property from the argument object so the call
only supplies emailAccount, threadMessages (threadMessagesForLLM), and
userSentLastEmail to match the updated function signature and other callers.
- Add operationId to chatCompletionStream calls in compose-autocomplete and summarise - Remove modelType parameter from isColdEmail, findMatchingRules, aiChooseRule, determineConversationStatus as it was removed from the API - Update match-rules.test.ts to remove modelType from test cases
We can add caching btw. Gemini 3 Flash could be a good model for you. It's cheap and outperforms Gemini 2.5 Pro, which is a tier 1 model till quite recently. |
|
More on caching here, but actually most of the time it's not helpful for us as the prompts are <1000 tokens so nothing gets cached :( I think we can keep this closed for now. I'm wary of making huge changes across the codebase with arguably small value. BTW, if you want to see what operations are costing you the most, you can set up Tinybird (for the llm analytics package). It tracks what your spend is going towards. |
Summary
Introduces a centralized LLM operation registry that maps each AI operation to a model tier (reasoning/fast/economy) with documented rationale. This replaces ad-hoc
getModel()calls with semanticgetModelForOperation()calls.Key Changes
operations.ts- Registry of 34 LLM operations with tier assignments, frequency metadata, and rationaleresolve-model.ts- Configuration resolver with env override supportLLM_OPERATION_OVERRIDESenv var - Allows operators to override specific operation tiers without code changesgetModelForOperation()CLAUDE.mdwith tier selection guidelines and model recommendationsTier Optimization Philosophy
Key insight: Frequency × Cost, but accuracy-critical operations stay on reasoning even at high volume to prevent trust erosion.
Operations Tier Assignments
Stay on reasoning (high-consequence per-email):
rule.match-email- Complex rule matching with negation logic. Wrong automation is high-consequencereply.determine-thread-status- Core Reply Zero feature with 9 interdependent rulesclean.decide-archive- Data loss risk if wrong. Trust criticalStay on reasoning (user-visible):
reply.draft,report.generate-summary,meeting.generate-briefingMoved to economy (high volume, recoverable errors):
rule.choose-args- Per-email structured extraction after rule matchreply.check-needs-reply- Binary classification, low consequencecategorize.cold-email- Multi-layer safety net, errors are recoverabledigest.summarize-email- Per-email summarizationMoved to fast (interactive):
assistant.chat- Interactive chat, user waitingrule.diff,rule.find-existing,rule.generate-prompt- Interactive UIExpected Annual Cost Savings (per user account)
Based on 75 emails/day (~27,375/year), 1.5K input + 0.2K output tokens per operation:
Per-email operations (7 total):
Recommended Models (January 2026)
Operator Override Example
Override specific operations without code changes:
```bash
LLM_OPERATION_OVERRIDES='{"categorize.cold-email":"reasoning"}'
```
Test Plan
Summary by CodeRabbit
New Features
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.