refactor(whatsapp): introduce shopper agent tool contract - #656
Conversation
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
|
Warning Review limit reached
Next review available in: 21 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughIntroduces typed shopper-agent tools, policy evaluation, audited intent planning, safe output validation, and WhatsApp integration for conversation routing and denied-tool handling. ChangesShopper agent planning and WhatsApp integration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant WhatsAppService
participant ShopperAgentOrchestrator
participant ShopperAgentPolicyService
participant Analytics
WhatsAppService->>ShopperAgentOrchestrator: planIntent(parsed intent and consent context)
ShopperAgentOrchestrator->>ShopperAgentPolicyService: evaluate tool request
ShopperAgentPolicyService-->>ShopperAgentOrchestrator: allowed or denied decision
ShopperAgentOrchestrator-->>WhatsAppService: tool or conversation plan
WhatsAppService->>Analytics: apply shopper-agent audit
WhatsAppService-->>WhatsAppService: handle denied tool or continue planned flow
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1b2fa4a387
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| : {}; | ||
| analytics.parsedFilters = { | ||
| ...existing, | ||
| shopperAgent: { |
There was a problem hiding this comment.
Keep planner audit data out of parsed filters
Every message that reaches intent planning, including menus, support requests, and natural answers, now persists a shopperAgent object in parsedFilters. This field represents discovery-derived filter signals used for analytics and future recommendation training, so non-search conversations will now appear to contain parsed filters and pollute that dataset; record planning decisions in a separate analytics/audit field instead.
AGENTS.md reference: apps/backend/src/channels/whatsapp/AGENTS.md:L603-L606
Useful? React with 👍 / 👎.
| for (const key of Object.keys(output)) { | ||
| if (!allowedFields.has(key)) { | ||
| throw new Error(`Unsafe ${name} tool output field: ${key}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
Validate output values before narrowing the type
This loop rejects unknown field names but never verifies required fields or their runtime values, so outputs such as {} or { status: "sent", productsShownCount: "two" } pass and are then narrowed to ShopperAgentToolOutputMap[Name]. When an executor returns malformed data, downstream code can therefore rely on fields that are absent or have the wrong type; validate each tool's required fields, status union, arrays, and scalar types before making this assertion.
Useful? React with 👍 / 👎.
| if (request.category === "guest") { | ||
| return { | ||
| allowed: true, | ||
| reason: "allowed", | ||
| confirmationRequired: false, |
There was a problem hiding this comment.
Derive access categories from trusted tool definitions
The policy trusts the caller-supplied request.category, while the exported ShopperAgentToolRequest type permits every tool name to carry any category. Any caller that constructs or deserializes { name: "get_order_status", category: "guest", ... } therefore receives an allowed decision without a linked account; the current registry happens to create the correct pairing, but the authorization boundary itself does not enforce it. Derive the category from the trusted definition for request.name, or bind each name to its literal category in the request type.
AGENTS.md reference: apps/backend/src/channels/whatsapp/AGENTS.md:L509-L511
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
apps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.ts (2)
175-196: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse a NestJS exception instead of plain
Error.These output-safety guards throw raw
Errorobjects. Per coding guidelines, domain-failure throws should use NestJS built-in exceptions.As per coding guidelines, "Use NestJS built-in exceptions and preserve the documented error codes for validation, authorization, payment, and domain failures."
♻️ Proposed fix
-import { Injectable } from "`@nestjs/common`"; +import { Injectable, InternalServerErrorException } from "`@nestjs/common`";if (!this.isRecord(output)) { - throw new Error(`Unsafe ${name} tool output`); + throw new InternalServerErrorException(`Unsafe ${name} tool output`); }if (FORBIDDEN_SAFE_OUTPUT_FIELDS.has(key)) { - throw new Error(`Unsafe shopper-agent output field: ${key}`); + throw new InternalServerErrorException( + `Unsafe shopper-agent output field: ${key}`, + ); }Also applies to: 229-245
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.ts` around lines 175 - 196, Update assertSafeOutput and the additional output-safety guard at the referenced later section to throw the appropriate NestJS built-in exception instead of plain Error, while preserving the existing unsafe-output messages and behavior.Source: Coding guidelines
129-141: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAdd a guard test for forbidden/safe field overlap.
Nothing currently asserts that
FORBIDDEN_SAFE_OUTPUT_FIELDSnever intersects a tool'ssafeOutputFields. A future addition to a tool's allowlist using a name that coincides with the blocklist would silently passassertSafeOutput(allowlist check happens after the forbidden-field check, but a field present in both sets would still be rejected only by luck of check order — worth locking this down explicitly).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.ts` around lines 129 - 141, Add a guard test around FORBIDDEN_SAFE_OUTPUT_FIELDS that iterates every tool’s safeOutputFields and asserts none overlap with the forbidden set. Ensure the test fails if any allowlisted field is later added to the blocklist, while preserving the existing assertSafeOutput behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@apps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.ts`:
- Around line 175-196: Update assertSafeOutput and the additional output-safety
guard at the referenced later section to throw the appropriate NestJS built-in
exception instead of plain Error, while preserving the existing unsafe-output
messages and behavior.
- Around line 129-141: Add a guard test around FORBIDDEN_SAFE_OUTPUT_FIELDS that
iterates every tool’s safeOutputFields and asserts none overlap with the
forbidden set. Ensure the test fails if any allowlisted field is later added to
the blocklist, while preserving the existing assertSafeOutput behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 7bf3d99f-49ee-4fe1-bfed-256a4c103ae1
📒 Files selected for processing (12)
apps/backend/src/channels/whatsapp/agent/shopper-agent-orchestrator.service.spec.tsapps/backend/src/channels/whatsapp/agent/shopper-agent-orchestrator.service.tsapps/backend/src/channels/whatsapp/agent/shopper-agent-policy.service.spec.tsapps/backend/src/channels/whatsapp/agent/shopper-agent-policy.service.tsapps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.spec.tsapps/backend/src/channels/whatsapp/agent/shopper-agent-tool.registry.tsapps/backend/src/channels/whatsapp/agent/shopper-agent.types.tsapps/backend/src/channels/whatsapp/whatsapp-intent.service.spec.tsapps/backend/src/channels/whatsapp/whatsapp-shared.module.tsapps/backend/src/channels/whatsapp/whatsapp.constants.tsapps/backend/src/channels/whatsapp/whatsapp.service.spec.tsapps/backend/src/channels/whatsapp/whatsapp.service.ts
What does this PR do?
Introduces the typed shopper-agent foundation for WIZZA so Gemini intent output is converted into a validated tool or conversation plan before existing WhatsApp handlers run. It adds stable tool input/output contracts, action categories, consent/account-link policy evaluation, shopper-safe output validation, and structured planning analytics while preserving the current text/image discovery, recent-product selection, consent, account-linking, and placeholder behavior. No new commerce mutation is enabled in this PR.
Type of change
Area affected
How to test this
cd apps/backend && pnpm exec jest --runInBand channels/whatsapp.pnpm exec eslint src/channels/whatsapp,pnpm run typecheck, andpnpm run buildfromapps/backend.Expected result: all existing discovery paths behave as before; guest tools run only after consent; linked tools initiate the existing account-linking flow for unlinked shoppers; unknown Gemini actions fall back safely; confirmation-required tools are classified but do not execute a new write.
Pre-commit checklist
console.logleft in production code.envfiles committedanytypes addeddb push(N/A: no schema or migration changes)Screenshots
N/A. This PR changes backend WhatsApp orchestration contracts only and has no UI changes.
Notes for reviewer
Foundation added
guest,linked-read,linked-write, andconfirmation-required.WhatsAppSharedModuleand integrated planning into the existingWhatsAppServicedispatch path.Behavior deliberately preserved
get_order_statusandconfirm_deliveryretain their existing linked-account placeholder behavior.confirm_deliveryis now marked confirmation-required, but this PR does not add or execute a delivery-confirmation write.Validation
dev.git diff --check: passed.No schema/migrations,
.env.local, secrets, private prompt values, frontend routes/UI, checkout/cart/payment behavior, payout math, delivery booking, Shipbubble, search ranking, image-search architecture, public product URLs, or store-owner behavior were touched.Summary by CodeRabbit
New Features
Bug Fixes
Tests