Skip to content

feat: Integrate Vercel Queues for asynchronous resolution search#702

Open
ngoiyaeric wants to merge 1 commit into
QueueLab:mainfrom
ngoiyaeric:feature/vercel-queues-integration
Open

feat: Integrate Vercel Queues for asynchronous resolution search#702
ngoiyaeric wants to merge 1 commit into
QueueLab:mainfrom
ngoiyaeric:feature/vercel-queues-integration

Conversation

@ngoiyaeric

@ngoiyaeric ngoiyaeric commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

This PR integrates Vercel Queues to enhance the asynchronous activity of the system, specifically by offloading the resolution search task to a background queue. This improves the responsiveness of the main application by deferring expensive geospatial analysis and news search operations.

Summary by CodeRabbit

  • New Features

    • Resolution searches now run in the background, so requests return faster while processing continues asynchronously.
    • Added queue-based handling for resolution search jobs to improve reliability and retry behavior.
  • Bug Fixes

    • Updated search handling to better support optional drawn areas and location details during processing.

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Someone is attempting to deploy a commit to the QCX-MAIN Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Manus AI Agent seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The resolution_search path in app/actions.tsx is refactored to enqueue a background job via a queue send call instead of directly invoking resolutionSearch. A new queue callback route processes the job by calling resolutionSearch, and vercel.json registers a queue trigger for the new route.

Changes

Resolution search queue offload

Layer / File(s) Summary
Enqueue resolution-search job from server action
app/actions.tsx
Removes direct resolutionSearch import/call, adds send and DrawnFeature type import, and replaces the inline streaming workflow with a queue enqueue call, marking streams done and returning early.
Queue callback route and trigger wiring
app/api/queues/resolution-search/route.ts, vercel.json
New POST route logs queue metadata, calls resolutionSearch with the message payload, returns success/result or re-throws for retry; vercel.json adds an experimentalTriggers entry with a queue/v2beta trigger for the resolution-search topic.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ServerAction as app/actions.tsx
  participant Queue
  participant Route as resolution-search route
  participant ResolutionSearch as resolutionSearch

  Client->>ServerAction: trigger resolution_search
  ServerAction->>Queue: send('resolution-search', payload)
  ServerAction-->>Client: mark task enqueued, close streams
  Queue->>Route: POST callback with message/metadata
  Route->>ResolutionSearch: resolutionSearch(messages, timezone, drawnFeatures, location)
  ResolutionSearch-->>Route: result
  Route-->>Queue: { status: 'success', result }
Loading

Possibly related PRs

  • QueueLab/QCX#316: Both PRs modify the resolution_search flow in app/actions.tsx, one wiring direct resolutionSearch execution and the other replacing it with a queue enqueue call.
  • QueueLab/QCX#492: Both PRs touch the same resolution_search processing logic in app/actions.tsx, overlapping on streaming/message handling that is now moved to the queue worker.
  • QueueLab/QCX#683: Both PRs modify how drawnFeatures are prepared and passed into resolutionSearch processing within app/actions.tsx.

Suggested labels: Review effort 3/5

Poem

A hop, a skip, a queued-up quest,
No more waiting, the action's at rest.
Off to the worker the message flies,
resolutionSearch beneath queue skies.
Vercel's trigger stands watch and true —
🐇 this rabbit's job queue is done, and new!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: moving resolution search to Vercel Queues asynchronously.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feature/vercel-queues-integration

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

❤️ Share

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

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Integrate Vercel Queues for async resolution search

✨ Enhancement ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Enqueue resolution search requests to Vercel Queues instead of running in-request.
• Add a queue callback API route to execute the resolution search in the background.
• Configure Vercel function triggers to bind the callback route to the queue topic.
Diagram

graph TD
  A["Client UI"] --> B["submit() server action"] --> C["@vercel/queue send()"] --> D["Vercel Queue topic: resolution-search"] --> E["Queue callback route"] --> F["resolutionSearch agent"]
  F -.-> G["Persist/notify results (TBD)"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Persist results + poll by job/chat id
  • ➕ Makes async processing usable end-to-end (user can retrieve results later)
  • ➕ Allows retries without losing output; supports audit/debugging
  • ➖ Requires database/storage design and a retrieval endpoint
  • ➖ Needs correlation ids (chatId/messageId) included in queue payload
2. Push results to client via realtime channel (webhook/WebSocket)
  • ➕ Best UX: results appear automatically when background job completes
  • ➕ Avoids polling load
  • ➖ Requires additional infra/service (e.g., Ably/Pusher/SSE)
  • ➖ More complex client state reconciliation with existing AI chat history
3. Keep synchronous path with a hard timeout fallback to queue
  • ➕ Preserves current immediate/streaming UX when jobs are fast
  • ➕ Queue only used when computation exceeds thresholds
  • ➖ Two execution paths to maintain/test
  • ➖ Still risks request timeouts under load

Recommendation: Queue offload is a good direction for responsiveness, but this PR currently enqueues work without any implemented result persistence/notification path (it only logs/returns from the callback). Consider adding a correlation id (e.g., chatId + groupeId/messageId) to the queue payload and persisting the resolutionSearch output so the UI can fetch and render the final summary/geojson later. Also verify the runtime dependency on @vercel/queue is present in the project dependencies and that the callback route is appropriately secured/validated for expected payload shape.

Files changed (3) +35 / -91

Enhancement (2) +26 / -91
actions.tsxOffload resolution search to Vercel Queue from server action +8/-91

Offload resolution search to Vercel Queue from server action

• Replaces inline 'resolutionSearch' execution/streaming UI updates with a queue 'send('resolution-search', ...)' call. Updates the user-facing status to indicate the task was enqueued and ends the UI streams early.

app/actions.tsx

route.tsAdd Vercel Queue callback handler for resolution search +18/-0

Add Vercel Queue callback handler for resolution search

• Introduces a POST handler via 'handleCallback' that consumes queued messages and runs 'resolutionSearch' asynchronously. Logs processing status and rethrows errors to allow Vercel retry behavior.

app/api/queues/resolution-search/route.ts

Other (1) +9 / -0
vercel.jsonConfigure Vercel queue trigger for resolution-search topic +9/-0

Configure Vercel queue trigger for resolution-search topic

• Adds an 'experimentalTriggers' configuration binding the queue/v2beta topic 'resolution-search' to the callback route function.

vercel.json

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Non-serializable queue result 🐞 Bug ≡ Correctness
Description
The queue callback returns the streamObject result wrapper from resolutionSearch instead of
materializing the final structured object, so the returned value is not a plain JSON payload and the
analysis result is never concretely produced/consumed. This can break the callback contract and
prevents any usable result from being stored or forwarded.
Code

app/api/queues/resolution-search/route.ts[R5-13]

+export const POST = handleCallback(async (message: { messages: CoreMessage[], timezone: string, drawnFeatures?: any[], location?: { lat: number, lng: number } }, metadata) => {
+  console.log(`Processing resolution search message ${metadata.messageId} for topic ${metadata.topicName}`);
+  try {
+    // Execute the resolutionSearch logic asynchronously
+    const result = await resolutionSearch(message.messages, message.timezone, message.drawnFeatures, message.location);
+    console.log(`Resolution search message ${metadata.messageId} processed successfully.`);
+    // In a real application, you would likely store this result in a database
+    // or notify the original requestor through a webhook or websocket.
+    return { status: 'success', result };
Evidence
The callback returns result directly, but resolutionSearch returns streamObject(...) (a
streaming wrapper), and existing code patterns show you must iterate the partial stream and/or await
.object to obtain a usable final object.

app/api/queues/resolution-search/route.ts[5-17]
lib/agents/resolution-search.tsx[41-90]
lib/agents/query-suggestor.tsx[19-50]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`resolutionSearch()` returns a `streamObject(...)` result (stream wrapper). The queue callback currently returns that wrapper directly (`{ status: 'success', result }`), which is not the final structured analysis and is unlikely to be JSON-serializable.

## Issue Context
Other call sites in the repo consume `partialObjectStream` and/or `await result.object` to obtain the final structured object.

## Fix Focus Areas
- app/api/queues/resolution-search/route.ts[5-17]
- lib/agents/resolution-search.tsx[41-90]

## Expected fix shape
- In the queue callback:
 - `const stream = await resolutionSearch(...)`
 - `const analysis = await stream.object` (and optionally iterate `partialObjectStream` if needed)
 - Persist `analysis` somewhere (DB/kv) keyed by correlation id, or publish/notify.
 - Return a small serializable ack (e.g., `{ status: 'success' }`) rather than the stream wrapper.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Queued results never surfaced 🐞 Bug ≡ Correctness
Description
The resolution_search server action now only enqueues a job and closes UI streams without writing
any assistant response/resolution_search_result messages to aiState, so chats won’t be saved
and there is no in-repo mechanism to later render the background result. Users will see “enqueued”
once, but the actual analysis output can’t appear in history/reload/share flows.
Code

app/actions.tsx[R105-110]

+        await send('resolution-search', { messages, timezone, drawnFeatures, location });
+        summaryStream.update('Resolution search task enqueued. Please check back later for results.');
+        summaryStream.done();
+        isGenerating.done(false);
+        uiStream.done();
+        return; // Exit early as the actual processing will happen in the queue
Evidence
The new flow enqueues and ends streams without any aiState.done that appends assistant
response/resolution_search_result messages; persistence is gated on having a response, and the
UI renderer only knows how to display resolution output when resolution_search_result exists.

app/actions.tsx[54-138]
app/actions.tsx[515-520]
app/actions.tsx[659-681]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new async queue path enqueues work and exits without updating `aiState` with an assistant `response` (required for persistence) or any placeholder/correlation data needed to later attach the real `resolution_search_result`.

## Issue Context
- `onSetAIState` only persists chats when there is at least one message with `type === 'response'`.
- UI reconstruction expects `resolution_search_result` messages to render the carousel + GeoJSON.

## Fix Focus Areas
- app/actions.tsx[54-138]
- app/actions.tsx[515-520]
- app/actions.tsx[659-681]
- app/api/queues/resolution-search/route.ts[5-17]

## Expected fix shape
- When enqueuing, include a correlation id (e.g., `chatId` + `groupeId` and/or userId) in the queue message.
- Update `aiState` with an assistant `response` like “Queued resolution search…” so the chat is persistable.
- Persist the queue result (in the callback) keyed by that correlation id.
- Add a retrieval mechanism (polling endpoint/server action) that loads the persisted result and appends a `resolution_search_result` message (and any related/followup messages) into `aiState` for UI rendering.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Invalid timezone retry storm 🐞 Bug ☼ Reliability
Description
The queue worker rethrows all errors to trigger retries, but resolutionSearch uses a user-provided
timezone in toLocaleString, which throws on invalid timezones; a permanently bad payload will be
retried repeatedly. This can cause avoidable queue backlogs and repeated failures from a single
malformed request.
Code

app/api/queues/resolution-search/route.ts[R14-17]

+  } catch (error) {
+    console.error(`Error processing resolution search message ${metadata.messageId}:`, error);
+    throw error; // Re-throw to trigger retry mechanism
+  }
Evidence
Timezone is taken from user input and passed into toLocaleString (which can throw), and the queue
handler rethrows any error specifically to trigger retries, making invalid inputs repeatedly
retried.

app/actions.tsx[55-61]
lib/agents/resolution-search.tsx[41-52]
app/api/queues/resolution-search/route.ts[14-17]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
User-provided `timezone` is used in `toLocaleString({ timeZone: timezone })` and may throw if invalid. The queue handler rethrows, which means the queue will retry a permanently invalid payload.

## Issue Context
Timezone is read from `formData` and passed through the queue message.

## Fix Focus Areas
- app/actions.tsx[55-61]
- lib/agents/resolution-search.tsx[42-51]
- app/api/queues/resolution-search/route.ts[14-17]

## Expected fix shape
- Validate/sanitize timezone before enqueuing and/or in the worker:
 - If invalid, default to `'UTC'`.
 - Optionally treat invalid timezone as non-retryable (ack with error stored for the requester) instead of throwing.
- Consider catching `RangeError` from `toLocaleString` inside `resolutionSearch` and falling back to `'UTC'`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Qodo Logo

Comment on lines +5 to +13
export const POST = handleCallback(async (message: { messages: CoreMessage[], timezone: string, drawnFeatures?: any[], location?: { lat: number, lng: number } }, metadata) => {
console.log(`Processing resolution search message ${metadata.messageId} for topic ${metadata.topicName}`);
try {
// Execute the resolutionSearch logic asynchronously
const result = await resolutionSearch(message.messages, message.timezone, message.drawnFeatures, message.location);
console.log(`Resolution search message ${metadata.messageId} processed successfully.`);
// In a real application, you would likely store this result in a database
// or notify the original requestor through a webhook or websocket.
return { status: 'success', result };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. Non-serializable queue result 🐞 Bug ≡ Correctness

The queue callback returns the streamObject result wrapper from resolutionSearch instead of
materializing the final structured object, so the returned value is not a plain JSON payload and the
analysis result is never concretely produced/consumed. This can break the callback contract and
prevents any usable result from being stored or forwarded.
Agent Prompt
## Issue description
`resolutionSearch()` returns a `streamObject(...)` result (stream wrapper). The queue callback currently returns that wrapper directly (`{ status: 'success', result }`), which is not the final structured analysis and is unlikely to be JSON-serializable.

## Issue Context
Other call sites in the repo consume `partialObjectStream` and/or `await result.object` to obtain the final structured object.

## Fix Focus Areas
- app/api/queues/resolution-search/route.ts[5-17]
- lib/agents/resolution-search.tsx[41-90]

## Expected fix shape
- In the queue callback:
  - `const stream = await resolutionSearch(...)`
  - `const analysis = await stream.object` (and optionally iterate `partialObjectStream` if needed)
  - Persist `analysis` somewhere (DB/kv) keyed by correlation id, or publish/notify.
  - Return a small serializable ack (e.g., `{ status: 'success' }`) rather than the stream wrapper.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread app/actions.tsx
Comment on lines +105 to +110
await send('resolution-search', { messages, timezone, drawnFeatures, location });
summaryStream.update('Resolution search task enqueued. Please check back later for results.');
summaryStream.done();
isGenerating.done(false);
uiStream.done();
return; // Exit early as the actual processing will happen in the queue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

2. Queued results never surfaced 🐞 Bug ≡ Correctness

The resolution_search server action now only enqueues a job and closes UI streams without writing
any assistant response/resolution_search_result messages to aiState, so chats won’t be saved
and there is no in-repo mechanism to later render the background result. Users will see “enqueued”
once, but the actual analysis output can’t appear in history/reload/share flows.
Agent Prompt
## Issue description
The new async queue path enqueues work and exits without updating `aiState` with an assistant `response` (required for persistence) or any placeholder/correlation data needed to later attach the real `resolution_search_result`.

## Issue Context
- `onSetAIState` only persists chats when there is at least one message with `type === 'response'`.
- UI reconstruction expects `resolution_search_result` messages to render the carousel + GeoJSON.

## Fix Focus Areas
- app/actions.tsx[54-138]
- app/actions.tsx[515-520]
- app/actions.tsx[659-681]
- app/api/queues/resolution-search/route.ts[5-17]

## Expected fix shape
- When enqueuing, include a correlation id (e.g., `chatId` + `groupeId` and/or userId) in the queue message.
- Update `aiState` with an assistant `response` like “Queued resolution search…” so the chat is persistable.
- Persist the queue result (in the callback) keyed by that correlation id.
- Add a retrieval mechanism (polling endpoint/server action) that loads the persisted result and appends a `resolution_search_result` message (and any related/followup messages) into `aiState` for UI rendering.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +14 to +17
} catch (error) {
console.error(`Error processing resolution search message ${metadata.messageId}:`, error);
throw error; // Re-throw to trigger retry mechanism
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remediation recommended

3. Invalid timezone retry storm 🐞 Bug ☼ Reliability

The queue worker rethrows all errors to trigger retries, but resolutionSearch uses a user-provided
timezone in toLocaleString, which throws on invalid timezones; a permanently bad payload will be
retried repeatedly. This can cause avoidable queue backlogs and repeated failures from a single
malformed request.
Agent Prompt
## Issue description
User-provided `timezone` is used in `toLocaleString({ timeZone: timezone })` and may throw if invalid. The queue handler rethrows, which means the queue will retry a permanently invalid payload.

## Issue Context
Timezone is read from `formData` and passed through the queue message.

## Fix Focus Areas
- app/actions.tsx[55-61]
- lib/agents/resolution-search.tsx[42-51]
- app/api/queues/resolution-search/route.ts[14-17]

## Expected fix shape
- Validate/sanitize timezone before enqueuing and/or in the worker:
  - If invalid, default to `'UTC'`.
  - Optionally treat invalid timezone as non-retryable (ack with error stored for the requester) instead of throwing.
- Consider catching `RangeError` from `toLocaleString` inside `resolutionSearch` and falling back to `'UTC'`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 (3)
app/actions.tsx (3)

76-105: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Base64 image payloads are forwarded through the queue message.

messages (pushed at line 98) contains the map screenshot(s) as base64 image content, and this whole array is sent via send('resolution-search', { messages, ... }). Vercel Queues bills and likely limits by message size — messages are metered in 4 KiB chunks, so a 12 KiB message counts as three operations. Base64-encoded map images can easily be hundreds of KB to a few MB, multiplying operation cost and risking size-limit failures on send.

Consider uploading the image(s) to blob storage first and passing a reference/URL in the queue payload instead of embedding the raw base64 data.

🤖 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 `@app/actions.tsx` around lines 76 - 105, The queued payload in
processResolutionSearch is too large because messages includes the user image as
base64 content. Update the flow in app/actions.tsx so the screenshot is uploaded
to blob/storage first, then replace the inline image data in the CoreMessage
used by send('resolution-search', ...) with a lightweight reference or URL. Keep
the existing message assembly and aiState update logic, but ensure the payload
sent through the queue only contains text plus the uploaded image reference.

103-121: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Resolution search results are computed but never delivered or persisted.

processResolutionSearch enqueues the job and immediately tells the user "check back later," but nothing ever relays the eventual result back to this client: resolutionSearch now runs inside a separate serverless invocation (the queue consumer route) that has no reference to uiStream/summaryStream from this request, and the consumer's own comment admits the result is discarded (app/api/queues/resolution-search/route.ts lines 11-12: "In a real application, you would likely store this result in a database or notify the original requestor"). Additionally, unlike every other branch in this file (lines 182-206, 335-346, 427-450), this path never calls aiState.done(), so the user's map-analysis message added at lines 91-97 is likely never finalized/committed to conversation state. The net effect: the feature silently produces no visible outcome and the conversation history is left inconsistent.

At minimum this needs a way to get the result back to the user (poll endpoint reading from a store, webhook, websocket, etc.) and a corresponding aiState.done()/update once the async result path completes.

🤖 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 `@app/actions.tsx` around lines 103 - 121, `processResolutionSearch` enqueues
work but never finalizes conversation state or delivers the eventual result, so
the user never sees an outcome. Update the `processResolutionSearch` flow in
`app/actions.tsx` to call `aiState.done()` in the async path just like the other
branches in this file, and add a persistence/return mechanism for the queued
`resolution-search` result so it can be retrieved by the client after the
separate serverless `resolution-search` consumer finishes. Use the existing
`summaryStream`/`uiStream` handling only for immediate status, and ensure the
eventual result is stored or published from the queue consumer instead of being
discarded.

105-118: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Avoid closing isGenerating/uiStream twice. The success path still falls through to finally, so these .done() calls run there again. Keep the cleanup in one place.

🤖 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 `@app/actions.tsx` around lines 105 - 118, The resolution-search success path
in the action is closing isGenerating and uiStream before returning, but the
finally block already does that cleanup, so they are being finalized twice.
Remove the early isGenerating.done(false) and uiStream.done() calls from the try
branch in app/actions.tsx and keep the cleanup centralized in the finally block,
preserving the early return after send('resolution-search', ...).
🤖 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.

Inline comments:
In `@app/actions.tsx`:
- Line 105: The resolution-search enqueue in processResolutionSearch currently
has no deduplication, so duplicate requests can create multiple jobs. Update the
send() call in processResolutionSearch to pass a stable idempotencyKey derived
from the request or groupeId, and keep it consistent for equivalent submissions
so retries or double submits are deduped by send(). Use the existing identifiers
processResolutionSearch and send('resolution-search', ...) to locate the call
site.

In `@app/api/queues/resolution-search/route.ts`:
- Around line 5-13: The POST handler in handleCallback currently computes
resolutionSearch and only returns the result, so the data is lost once the
callback acknowledges the message. Update the resolution-search flow in
POST/resolutionSearch to persist the result or forward it to the requester using
a durable path such as a database/cache keyed by the message/job id, or a
webhook/websocket notification. Make sure the enqueue/send side in
app/actions.tsx and the consumer side agree on a shared identifier so the caller
can later fetch or receive the stored result instead of relying on the handler
return value.
- Line 5: The POST handler in resolution-search/route.ts is typing drawnFeatures
as any[], which breaks the shared payload contract. Update the message parameter
in the handleCallback callback to reuse the DrawnFeature type from `@/lib/agents`
instead of any[], matching the type used by app/actions.tsx. This keeps the
producer and consumer in sync and preserves compile-time validation of the
drawnFeatures shape.

---

Outside diff comments:
In `@app/actions.tsx`:
- Around line 76-105: The queued payload in processResolutionSearch is too large
because messages includes the user image as base64 content. Update the flow in
app/actions.tsx so the screenshot is uploaded to blob/storage first, then
replace the inline image data in the CoreMessage used by
send('resolution-search', ...) with a lightweight reference or URL. Keep the
existing message assembly and aiState update logic, but ensure the payload sent
through the queue only contains text plus the uploaded image reference.
- Around line 103-121: `processResolutionSearch` enqueues work but never
finalizes conversation state or delivers the eventual result, so the user never
sees an outcome. Update the `processResolutionSearch` flow in `app/actions.tsx`
to call `aiState.done()` in the async path just like the other branches in this
file, and add a persistence/return mechanism for the queued `resolution-search`
result so it can be retrieved by the client after the separate serverless
`resolution-search` consumer finishes. Use the existing
`summaryStream`/`uiStream` handling only for immediate status, and ensure the
eventual result is stored or published from the queue consumer instead of being
discarded.
- Around line 105-118: The resolution-search success path in the action is
closing isGenerating and uiStream before returning, but the finally block
already does that cleanup, so they are being finalized twice. Remove the early
isGenerating.done(false) and uiStream.done() calls from the try branch in
app/actions.tsx and keep the cleanup centralized in the finally block,
preserving the early return after send('resolution-search', ...).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3ae4b022-35e5-4832-995d-cda2fa1a9ac2

📥 Commits

Reviewing files that changed from the base of the PR and between 79065e7 and eca0ffa.

📒 Files selected for processing (3)
  • app/actions.tsx
  • app/api/queues/resolution-search/route.ts
  • vercel.json
📜 Review details
⚠️ CI failures not shown inline (1)

Commit Status: Vercel – qcx: Vercel – qcx

Conclusion: failure

Authorization required to deploy.
🔇 Additional comments (3)
app/actions.tsx (1)

15-16: LGTM!

app/api/queues/resolution-search/route.ts (1)

1-18: Queue trigger/consumer setup matches the documented pattern.

handleCallback usage, the metadata fields, and throw-to-retry semantics align with the published SDK reference, and the route's queue trigger makes it unreachable from the public internet, so no extra auth is needed here.

vercel.json (1)

1-9: LGTM!

Comment thread app/actions.tsx
}
]
});
await send('resolution-search', { messages, timezone, drawnFeatures, location });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider an idempotency key for the enqueue call.

send() supports deduplication: idempotencyKey provides deduplication for the full retention window. Since processResolutionSearch is fire-and-forget and not obviously guarded against duplicate invocation (e.g., double form submits, client retries), passing a stable idempotencyKey (derived from the request/groupeId) would prevent duplicate resolution-search jobs from being enqueued and processed.

🤖 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 `@app/actions.tsx` at line 105, The resolution-search enqueue in
processResolutionSearch currently has no deduplication, so duplicate requests
can create multiple jobs. Update the send() call in processResolutionSearch to
pass a stable idempotencyKey derived from the request or groupeId, and keep it
consistent for equivalent submissions so retries or double submits are deduped
by send(). Use the existing identifiers processResolutionSearch and
send('resolution-search', ...) to locate the call site.

import { resolutionSearch } from '@/lib/agents/resolution-search';
import { CoreMessage } from 'ai';

export const POST = handleCallback(async (message: { messages: CoreMessage[], timezone: string, drawnFeatures?: any[], location?: { lat: number, lng: number } }, metadata) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Type the message payload with DrawnFeature[] instead of any[].

drawnFeatures?: any[] loses the type contract that app/actions.tsx establishes via import { ..., type DrawnFeature } from '@/lib/agents'. Importing and reusing DrawnFeature here keeps the producer/consumer payload contract in sync and catches shape drift at compile time.

♻️ Proposed fix
 import { handleCallback } from '`@vercel/queue`';
 import { resolutionSearch } from '`@/lib/agents/resolution-search`';
 import { CoreMessage } from 'ai';
+import type { DrawnFeature } from '`@/lib/agents/resolution-search`';

-export const POST = handleCallback(async (message: { messages: CoreMessage[], timezone: string, drawnFeatures?: any[], location?: { lat: number, lng: number } }, metadata) => {
+export const POST = handleCallback(async (message: { messages: CoreMessage[], timezone: string, drawnFeatures?: DrawnFeature[], location?: { lat: number, lng: number } }, metadata) => {
📝 Committable suggestion

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

Suggested change
export const POST = handleCallback(async (message: { messages: CoreMessage[], timezone: string, drawnFeatures?: any[], location?: { lat: number, lng: number } }, metadata) => {
import { handleCallback } from '`@vercel/queue`';
import { resolutionSearch } from '`@/lib/agents/resolution-search`';
import { CoreMessage } from 'ai';
import type { DrawnFeature } from '`@/lib/agents/resolution-search`';
export const POST = handleCallback(async (message: { messages: CoreMessage[], timezone: string, drawnFeatures?: DrawnFeature[], location?: { lat: number, lng: number } }, metadata) => {
🤖 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 `@app/api/queues/resolution-search/route.ts` at line 5, The POST handler in
resolution-search/route.ts is typing drawnFeatures as any[], which breaks the
shared payload contract. Update the message parameter in the handleCallback
callback to reuse the DrawnFeature type from `@/lib/agents` instead of any[],
matching the type used by app/actions.tsx. This keeps the producer and consumer
in sync and preserves compile-time validation of the drawnFeatures shape.

Comment on lines +5 to +13
export const POST = handleCallback(async (message: { messages: CoreMessage[], timezone: string, drawnFeatures?: any[], location?: { lat: number, lng: number } }, metadata) => {
console.log(`Processing resolution search message ${metadata.messageId} for topic ${metadata.topicName}`);
try {
// Execute the resolutionSearch logic asynchronously
const result = await resolutionSearch(message.messages, message.timezone, message.drawnFeatures, message.location);
console.log(`Resolution search message ${metadata.messageId} processed successfully.`);
// In a real application, you would likely store this result in a database
// or notify the original requestor through a webhook or websocket.
return { status: 'success', result };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Implement the TODO: persist or forward the result.

The comment on lines 11-12 correctly flags that result needs to be stored or relayed back to the requester — right now it's just returned from the handler, which only acknowledges the message per Return normally = acknowledge // Throw = retry with backoff; the value itself has no destination. This is the other half of the enqueue-side gap flagged in app/actions.tsx (lines 103-121) — without persistence (DB row, cache entry, webhook/websocket push), the resolution search result is unrecoverable once this consumer returns.

Want me to sketch a persistence + notification path (e.g., write to a datastore keyed by a job id returned from send, then have the client poll or subscribe)?

🤖 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 `@app/api/queues/resolution-search/route.ts` around lines 5 - 13, The POST
handler in handleCallback currently computes resolutionSearch and only returns
the result, so the data is lost once the callback acknowledges the message.
Update the resolution-search flow in POST/resolutionSearch to persist the result
or forward it to the requester using a durable path such as a database/cache
keyed by the message/job id, or a webhook/websocket notification. Make sure the
enqueue/send side in app/actions.tsx and the consumer side agree on a shared
identifier so the caller can later fetch or receive the stored result instead of
relying on the handler return value.

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
qcx Error Error Jul 6, 2026 1:08pm

@ngoiyaeric

ngoiyaeric commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai 16:07:58.771 Running build in Washington, D.C., USA (East) – iad1
16:07:58.771 Build machine configuration: 2 cores, 8 GB
16:07:58.891 Cloning github.com/QueueLab/QCX (Branch: feature/vercel-queues-integration, Commit: eca0ffa)
16:07:59.867 Cloning completed: 976.000ms
16:08:02.408 Restored build cache from previous deployment (5684bZZhR6BrrnKsAKxTtZs3coxQ)
16:08:02.610 Running "vercel build"
16:08:02.632 Vercel CLI 54.19.0
16:08:03.055 Running "install" command: bun install...
16:08:03.090 [0.84ms] ".env"
16:08:03.094 bun install v1.3.12 (700fc117)
16:08:03.460 Saved lockfile
16:08:03.460
16:08:03.461 Checked 1294 installs across 1318 packages (no changes) [383.00ms]
16:08:03.464 Detected Next.js version: 15.3.8
16:08:03.464 Running "bun run build"
16:08:03.470 $ next build
16:08:04.350 ▲ Next.js 15.3.8
16:08:04.351 - Environments: .env
16:08:04.351
16:08:04.408 Creating an optimized production build ...
16:08:37.896 Failed to compile.
16:08:37.897
16:08:37.897 ./app/actions.tsx
16:08:37.897 Module not found: Can't resolve '@vercel/queue'
16:08:37.897
16:08:37.897 https://nextjs.org/docs/messages/module-not-found
16:08:37.897
16:08:37.898 Import trace for requested module:
16:08:37.898 ./app/page.tsx
16:08:37.898
16:08:37.898 ./app/api/queues/resolution-search/route.ts
16:08:37.898 Module not found: Can't resolve '@vercel/queue'
16:08:37.898
16:08:37.898 https://nextjs.org/docs/messages/module-not-found
16:08:37.899
16:08:37.928
16:08:37.929 > Build failed because of webpack errors
16:08:37.943 error: script "build" exited with code 1
16:08:37.968 Error: Command "bun run build" exited with 1
also resolve actions.tsx conflict

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Failed to resolve merge conflicts

Unexpected error: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)

Please resolve conflicts manually.

Copy link
Copy Markdown
Collaborator Author

Closing this pull request as part of a bulk cleanup of pending PRs.

@ngoiyaeric ngoiyaeric closed this Jul 9, 2026
@ngoiyaeric ngoiyaeric reopened this Jul 9, 2026
@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants