-
-
Notifications
You must be signed in to change notification settings - Fork 7
feat: Create GeoJSON Enrichment Agent #312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This commit introduces a new asynchronous agent, `geojsonEnricher`, responsible for processing textual responses and extracting structured geospatial data. Key changes: - Created a new `geojsonEnricher` agent in `lib/agents/geojson-enricher.tsx`. - Defined new TypeScript interfaces for `LocationResponse`, `MapCommand`, and GeoJSON objects in `lib/types/custom.ts`. - Integrated the new agent into the main application workflow in `app/actions.tsx`. - Added a `LocationResponseHandler` component to update the map state via the `MapDataContext`. - Updated the `Mapbox` component to render GeoJSON data and execute map commands.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds a multi-agent Map Control Orchestrator, worker modules, and a Composio Mapbox integration; introduces Zod schemas for map data, geojson enrichment (v1/v2), map command generation, map feedback capture/emit, map UI wiring (LocationResponseHandler, MapStatus, mapbox command execution), tests, and documentation. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant App as app/actions.tsx
participant Orch as MapControlOrchestrator / runMapGraph
participant Parser as GeoJSON Parser
participant Generator as Map Command Generator
participant Validator as Validator
participant Map as Mapbox Map (mapbox-map.tsx)
participant Analyzer as Feedback Analyzer
User->>App: submit researcher response
App->>Orch: process(researcherResponse) / runMapGraph(...)
Orch->>Parser: geojsonParser(text)
Parser-->>Orch: geojson + confidence
Orch->>Generator: mapCommandGenerator(context)
Generator-->>Orch: map_commands
Orch->>Validator: validateGeoJSON / validateMapCommands
Validator-->>Orch: ValidationResult
Orch-->>App: LocationResponse (text, geojson, map_commands)
App->>Map: setMapData(geojson, mapCommands)
Map->>Map: execute commands (flyTo/fitBounds/etc.)
Map-->>App: feedbackCallback(MapStateFeedback)
alt feedback loop enabled
App->>Orch: processFeedback(feedback)
Orch->>Analyzer: feedbackAnalyzer(feedback + context)
Analyzer-->>Orch: FeedbackAnalysis
Orch-->>App: refined LocationResponse or abort
end
App->>User: emit geojsonEnrichment tool output / update UI
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
|
|
Overall, great progress expanding the application’s geospatial functionality. With a couple more tests and error-handling tweaks, this will be even more robust. |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
||||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
bun.lockbis excluded by!**/bun.lockbpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (7)
app/actions.tsx(6 hunks)components/map/location-response-handler.tsx(1 hunks)components/map/map-data-context.tsx(2 hunks)components/map/mapbox-map.tsx(1 hunks)lib/agents/geojson-enricher.tsx(1 hunks)lib/types/custom.ts(1 hunks)package.json(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
lib/agents/geojson-enricher.tsx (2)
lib/types/custom.ts (1)
LocationResponse(29-33)lib/utils/index.ts (1)
getModel(19-62)
components/map/location-response-handler.tsx (4)
lib/types/custom.ts (1)
LocationResponse(29-33)components/map/map-data-context.tsx (1)
useMapData(42-48)components/map/map-query-handler.tsx (4)
useMapData(30-83)toolOutput(33-74)McpResponseData(8-16)prevData(39-49)lib/agents/tools/geospatial.tsx (1)
McpResponse(22-25)
components/map/map-data-context.tsx (1)
lib/types/custom.ts (2)
GeoJSONFeatureCollection(23-26)MapCommand(2-5)
app/actions.tsx (5)
lib/agents/geojson-enricher.tsx (1)
geojsonEnricher(36-66)components/map/location-response-handler.tsx (1)
LocationResponseHandler(11-28)lib/agents/tools/geospatial.tsx (3)
uiStream(144-269)McpResponse(22-25)Location(15-20)components/map/map-query-handler.tsx (3)
toolOutput(33-74)useMapData(30-83)prevData(39-49)lib/agents/tools/index.tsx (1)
ToolProps(14-39)
🔇 Additional comments (5)
lib/agents/geojson-enricher.tsx (3)
1-3: LGTM!Imports are correctly typed and used appropriately throughout the file.
39-45: LGTM!Model initialization and message construction are correct. The type assertion is safe given
getModel()always returns aLanguageModel-compatible object.
57-65: LGTM!Error handling is appropriate: the function logs the error and returns a safe fallback with the original text, ensuring graceful degradation if enrichment fails.
app/actions.tsx (2)
15-15: LGTM!Import statement is correct and the function is properly used at line 301.
29-29: LGTM!Import statement is correct and the component is used at lines 556 and 591.
|
@coderabbitai generate unit tests |
|
@coderabbitai the code is not building |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 9590 @ngoiyaeric, I found the build issue! There's a syntax error in The ProblemThe import statement is missing a space between import { getModel }from '../utils';
// ↑ missing space hereThe FixChange Line 2 in import { getModel }from '../utils';to: import { getModel } from '../utils';This is a simple typo that will prevent TypeScript from compiling. Once you add the space, the build should succeed. |
|
Unit Test Generation failed: CodeRabbit timed out while planning changes for components/map/map-data-context.test.tsx. Skipping this file. |
✅ Actions performedUnit Test Generation started... |
|
Unit Test Generation failed: CodeRabbit timed out while planning changes for components/map/location-response-handler.test.tsx. Skipping this file. |
✅ Actions performedUnit Test Generation started... |
|
Unit Test Generation failed: CodeRabbit timed out while planning changes for app/actions.test.tsx. Skipping this file. |
✅ Actions performedUnit Test Generation started... |
This commit introduces a new asynchronous agent, `geojsonEnricher`, responsible for processing textual responses and extracting structured geospatial data. Key changes: - Created a new `geojsonEnricher` agent in `lib/agents/geojson-enricher.tsx`. - Defined new TypeScript interfaces for `LocationResponse`, `MapCommand`, and GeoJSON objects in `lib/types/custom.ts`. - Integrated the new agent into the main application workflow in `app/actions.tsx` with robust error handling. - Added a `LocationResponseHandler` component to update the map state via the `MapDataContext`. - Updated the `Mapbox` component to render GeoJSON data and execute map commands. NOTE: The application build is currently failing due to a persistent 'JavaScript heap out of memory' error. This appears to be a pre-existing issue with the repository's dependencies or build configuration that could not be resolved. The feature code is complete, but the build itself will need to be addressed separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
app/actions.tsx (1)
539-548: Wraprelatedcase in a block to satisfy no-switch-declarations.Declaring
const relatedQueries = …directly under thecasetriggers Biome’snoSwitchDeclarationserror because the binding leaks into other branches. Enclose thecase 'related'body in braces (or extract to a helper) so the declaration stays scoped to that branch.
♻️ Duplicate comments (1)
components/map/mapbox-map.tsx (1)
585-595: GuardfitBoundsparams before destructuring.
command.paramscan beundefinedwhen enrichment returns a malformed or partial command. Destructuring it directly throws and prevents later commands from running. Add a clause block, validatebounds, and bail early if missing.- case 'fitBounds': - const { bounds, options } = command.params; - mapInstance.fitBounds(bounds, options || {}); - break; + case 'fitBounds': { + const params = command.params ?? {}; + const { bounds, options } = params as { + bounds?: mapboxgl.FitBoundsLike; + options?: mapboxgl.FitBoundsOptions; + }; + if (!bounds) { + console.warn('fitBounds command missing bounds payload', command); + break; + } + mapInstance.fitBounds(bounds, options ?? {}); + break; + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockbis excluded by!**/bun.lockb
📒 Files selected for processing (4)
app/actions.tsx(7 hunks)components/map/mapbox-map.tsx(1 hunks)lib/agents/geojson-enricher.tsx(1 hunks)package.json(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
components/map/mapbox-map.tsx (2)
components/map/map-data-context.tsx (1)
MapData(7-17)components/map/map-query-handler.tsx (4)
prevData(39-49)prevData(53-57)toolOutput(33-74)useMapData(30-83)
lib/agents/geojson-enricher.tsx (2)
lib/types/custom.ts (1)
LocationResponse(29-33)lib/utils/index.ts (1)
getModel(19-62)
app/actions.tsx (3)
lib/agents/geojson-enricher.tsx (1)
geojsonEnricher(37-75)lib/schema/related.tsx (1)
PartialRelated(13-13)components/map/location-response-handler.tsx (1)
LocationResponseHandler(11-28)
🪛 Biome (2.1.2)
components/map/mapbox-map.tsx
[error] 593-593: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
app/actions.tsx
[error] 539-539: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
|
@coderabbitai review pr |
|
@ngoiyaeric, I'll review the PR changes for you. ✅ Actions performedReview triggered.
|
ngoiyaeric
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there should be a zod schema for the GeoJson parsing to improve its precision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/map/mapbox-map.tsx (1)
772-821: Duplicate useEffect with identical cleanup logic.This useEffect block duplicates the cleanup logic from the earlier initialization effect (lines 491-521). The effect body contains only placeholder comments (
// ... existing useEffect logic ...) but includes a full cleanup function, creating duplicate cleanup handlers.This will cause:
- Double cleanup execution when dependencies change
- Errors when attempting to remove already-removed controls
- Potential race conditions between the two cleanup functions
🐛 Recommended fix: Remove the duplicate effect
Remove lines 772-821 entirely, as the cleanup is already handled by the initialization effect at lines 491-521.
- // Cleanup for the main useEffect - useEffect(() => { - // ... existing useEffect logic ... - return () => { - // ... existing cleanup logic ... - if (longPressTimerRef.current) { // Cleanup timer on component unmount - clearTimeout(longPressTimerRef.current); - longPressTimerRef.current = null; - } - // ... existing cleanup logic for map and geolocation ... - if (map.current) { - map.current.off('moveend', captureMapCenter) - // ... rest of cleanup ... - } - // ... - }; - }, [ - handleUserInteraction, - startRotation, - stopRotation, - mapType, - updateMeasurementLabels, - setupGeolocationWatcher, - captureMapCenter, - setupDrawingTools, - setIsMapLoaded, - setMap - ]);Instead, add the long-press timer cleanup to the existing effect at line 491:
return () => { + if (longPressTimerRef.current) { + clearTimeout(longPressTimerRef.current); + longPressTimerRef.current = null; + } if (map.current) { map.current.off('moveend', captureMapCenter) // ... rest of existing cleanup
🤖 Fix all issues with AI agents
In @components/map/mapbox-map.tsx:
- Around line 677-685: Replace the use of any for the feedback object by
defining a proper interface (e.g., MapFeedback) that matches the shape used in
this block (success: boolean, currentCenter: [number, number], currentZoom:
number, currentPitch: number, currentBearing: number, error: Error | null |
string, timestamp: number) and annotate the feedback variable as that interface
(const feedback: MapFeedback = {...}); ensure the types of center,
executionError and the return types from mapInstance.getZoom/getPitch/getBearing
are compatible or cast appropriately so the assignment is type-safe.
- Around line 672-720: The feedbackHandler can run twice due to both
mapInstance.once('moveend') and the fallback setTimeout; add a local guard flag
(e.g., handled = false) captured by the closure and check/set it at the start of
feedbackHandler to ensure idempotence, cancel the timeout when moveend fires
(store the timeout id from setTimeout and clearTimeout in the moveend path), and
extend/remove the short 100ms fallback for long animations (or change it to a
longer configurable timeout); update references in this block (feedbackHandler,
mapInstance.once('moveend'), mapInstance.off('moveend'), the setTimeout call,
and the setMapData/mapData.feedbackCallback usage) to use the guard and timeout
cancellation so feedback is only sent once.
- Around line 610-629: The useEffect in the MapboxMap component currently emits
several console.log calls (e.g., the initial `'🗺️ Map commands useEffect
triggered:'` log, the `'⚠️ Skipping command execution'` log, and per-command
execution logs inside the loop referencing mapData.mapCommands and map.current);
remove these console.logs or gate them behind a configurable debug flag (e.g., a
local isDebug prop/env check such as process.env.NODE_ENV !== 'production' or a
passed debugLogs boolean) and replace with a proper logger if needed so
production builds do not output or incur the cost of these messages; ensure any
conditional checks use that flag before logging and keep the control flow and
error handling (executionError, try/catch) unchanged.
- Around line 566-572: The current code casts mapData.geojson to any when
calling (source as mapboxgl.GeoJSONSource).setData(...) and when passing data to
mapInstance.addSource, which loses type safety; instead, type mapData.geojson
with proper GeoJSON types (e.g., GeoJSON.FeatureCollection or GeoJSON.GeoJSON)
or Mapbox's mapboxgl.GeoJSONSourceRaw, import the GeoJSON types from 'geojson'
or use the types from 'mapbox-gl', and pass that typed value to setData and
addSource without using any; update the mapData type definition and replace the
any casts in the calls to use the correct typed object so setData and addSource
receive a properly typed GeoJSON value.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (1)
components/map/mapbox-map.tsx
🔇 Additional comments (1)
components/map/mapbox-map.tsx (1)
574-598: No Polygon layer for GeoJSON rendering.The code adds layers for
PointandLineStringfeatures but not forPolygon. If the GeoJSON enrichment produces polygon geometries, they will not be visible on the map.Consider adding a polygon fill/outline layer if needed:
+ // Add layer for polygons + mapInstance.addLayer({ + id: 'geojson-polygons', + type: 'fill', + source: 'geojson-data', + paint: { + 'fill-color': '#007cbf', + 'fill-opacity': 0.3, + }, + filter: ['==', '$type', 'Polygon'], + });
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
components/map/mapbox-map.tsx (1)
795-841: Duplicate cleanup effect may cause double cleanup attempts.This cleanup effect (lines 795-841) has nearly identical logic and the same dependency array as the main initialization effect's cleanup (lines 498-528). When the component unmounts or dependencies change, both cleanups will run, potentially causing errors when trying to remove already-removed resources.
Consider consolidating cleanup logic into a single effect or ensuring guards prevent double-cleanup.
app/actions.tsx (1)
629-639: Switch case declaration should be wrapped in a block.As flagged by Biome, declaring
relatedQueriesdirectly in the switch case allows other cases to potentially access it. Wrap the case body in a block.Proposed fix
- case 'related': - const relatedQueries = createStreamableValue<PartialRelated>() - relatedQueries.done(JSON.parse(content as string)) - return { - id, - component: ( - <Section title="Related" separator={true}> - <SearchRelated relatedQueries={relatedQueries.value} /> - </Section> - ) - } + case 'related': { + const relatedQueries = createStreamableValue<PartialRelated>() + relatedQueries.done(JSON.parse(content as string)) + return { + id, + component: ( + <Section title="Related" separator={true}> + <SearchRelated relatedQueries={relatedQueries.value} /> + </Section> + ) + } + }
🤖 Fix all issues with AI agents
In @app/actions.tsx:
- Around line 311-312: Replace the unsafe cast "(inquiry as any)?.question" by
giving inquiry a proper type: update the inquire function's return type (or the
variable declaration for inquiry) to an interface/type that includes the
question property (e.g., type Inquiry = { question: string; ... }) and use that
type instead of any; alternatively add a narrow type guard function
isInquiry(x): x is Inquiry and use if (isInquiry(inquiry)) inquiry.question.
Ensure usages reference the inquire function and the inquiry variable so
TypeScript enforces the presence of question rather than using "any".
- Around line 16-17: The file contains unused imports mapControlOrchestrator and
initializeComposioMapbox; remove these two import statements from
app/actions.tsx to eliminate dead code and unused dependencies, ensuring no
other references to mapControlOrchestrator or initializeComposioMapbox remain in
that file and run a quick build/typecheck to confirm there are no missing
usages.
In @components/map/mapbox-map.tsx:
- Around line 677-739: The effect can leak because fallbackTimeoutId and the
moveend listener referencing feedbackHandler aren't cleaned up on
re-run/unmount; declare feedbackHandler and fallbackTimeoutId in stable scope
(or store them in refs) so the cleanup can access the same references, and in
the effect return clearTimeout(fallbackTimeoutId) and remove the listener via
mapInstance.off('moveend', feedbackHandler) (also set handled = true or similar
guard) to ensure the timeout and event listener are always removed; adjust
placement so feedbackHandler is defined outside try/async blocks or use useRef
to hold the handler before registering mapInstance.once.
- Around line 642-651: The fitBoundsOptions variable is currently typed as any
which loses type safety; change its declaration to use mapboxgl.FitBoundsOptions
(e.g., initialize as {} as mapboxgl.FitBoundsOptions) and keep assigning
padding/duration/otherOptions into it before calling
mapInstance.fitBounds(bounds, fitBoundsOptions); ensure mapboxgl is available in
scope (import or reference the global type) so the compiler recognizes
mapboxgl.FitBoundsOptions and you get IDE/type checking for the fitBounds call.
- Around line 559-609: The effect that updates the 'geojson-data' source must
guard against style not being loaded and also add polygon support: check
map.current and use mapInstance.isStyleLoaded() (or listen for the 'styledata'
event) before calling addSource/addLayer to avoid race errors, and if waiting
for style, retry or attach a one-time listener; when adding layers, add two
polygon layers (e.g., 'geojson-polygons-fill' with type 'fill' and appropriate
'fill-color'/'fill-opacity' and 'geojson-polygons-outline' with type 'line' for
stroke) and use filters for 'Polygon' and 'MultiPolygon'; also ensure you remove
those polygon layers when mapData.geojson is falsy and when updating source,
mirror existing logic for points and lines using the same source id
'geojson-data'.
- Around line 19-28: Replace the local MapFeedback interface with the
centralized MapStateFeedback type: remove the MapFeedback interface declaration
in mapbox-map.tsx, add an import for MapStateFeedback from
"lib/types/map-schemas", and update any usages or annotations that reference
MapFeedback to use MapStateFeedback instead (no code logic changes needed since
all fields are provided where used).
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
app/actions.tsxcomponents/map/mapbox-map.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/map/mapbox-map.tsx (4)
components/map-toggle-context.tsx (1)
useMapToggle(36-42)components/map/map-data-context.tsx (1)
useMapData(55-61)components/map-loading-context.tsx (1)
useMapLoading(20-26)components/map/geojson-layer.tsx (4)
map(16-97)map(89-96)map(24-80)GeoJsonLayer(13-100)
🪛 Biome (2.1.2)
app/actions.tsx
[error] 630-631: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (3)
app/actions.tsx (3)
688-708: LGTM with suggestion: Consider validatingmap_commandsfield.The runtime validation for
geojsonEnrichmentis good defensive programming. However, consider also validating themap_commandsfield (should benullor an array) to ensure complete validation of theLocationResponsestructure.Optional enhancement
if ( toolOutput && typeof toolOutput.text === 'string' && (toolOutput.geojson === null || (typeof toolOutput.geojson === 'object' && - toolOutput.geojson.type === 'FeatureCollection')) + toolOutput.geojson.type === 'FeatureCollection')) && + (toolOutput.map_commands === null || + Array.isArray(toolOutput.map_commands)) ) {
420-426: LGTM!The tool message structure for
geojsonEnrichmentfollows the existing pattern and correctly stores the fulllocationResponsefor later UI rendering.
386-399: The fallback object property naming is correct—theLocationResponseSchemaexplicitly defines the property asmap_commandsin snake_case, matching the fallback object structure. No inconsistency exists here.The
console.logon line 390 is consistent with the logging pattern used throughout the file (lines 392, 553, 705, 734, 740), so it's not a unique concern to this location. If production logging needs to be reviewed, it should be done as a broader refactoring across the file rather than isolated to this enrichment flow.Likely an incorrect or invalid review comment.
app/actions.tsx
Outdated
| import { mapControlOrchestrator } from '@/lib/agents/map-control-orchestrator' | ||
| import { initializeComposioMapbox } from '@/mapbox_mcp/composio-mapbox' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n 'mapControlOrchestrator|initializeComposioMapbox' --type=ts --type=tsx -g '!*.test.*'Repository: QueueLab/QCX
Length of output: 83
🏁 Script executed:
rg -n 'mapControlOrchestrator|initializeComposioMapbox' -t ts -g '!*.test.*'Repository: QueueLab/QCX
Length of output: 1227
🏁 Script executed:
rg -n 'mapControlOrchestrator\(|initializeComposioMapbox\(' app/actions.tsxRepository: QueueLab/QCX
Length of output: 38
Remove unused imports: mapControlOrchestrator and initializeComposioMapbox are imported at lines 16-17 but never invoked in this file.
While these functions are used elsewhere in the codebase, they add dead code in app/actions.tsx and should be removed unless they're intended for future use.
🤖 Prompt for AI Agents
In @app/actions.tsx around lines 16 - 17, The file contains unused imports
mapControlOrchestrator and initializeComposioMapbox; remove these two import
statements from app/actions.tsx to eliminate dead code and unused dependencies,
ensuring no other references to mapControlOrchestrator or
initializeComposioMapbox remain in that file and run a quick build/typecheck to
confirm there are no missing usages.
| interface MapFeedback { | ||
| success: boolean; | ||
| currentCenter: [number, number]; | ||
| currentZoom: number; | ||
| currentPitch: number; | ||
| currentBearing: number; | ||
| currentBounds?: [[number, number], [number, number]]; | ||
| error?: string; | ||
| timestamp: number; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and read map-schemas file to check MapStateFeedback definition
fd -e ts -e tsx 'map-schemas' --type fRepository: QueueLab/QCX
Length of output: 80
🏁 Script executed:
#!/bin/bash
# Search for MapStateFeedback definition
rg "MapStateFeedback" --type ts --type tsx -B 2 -A 10Repository: QueueLab/QCX
Length of output: 83
🏁 Script executed:
#!/bin/bash
# Search for MapFeedback definition and usage in mapbox-map.tsx
rg "MapFeedback" --type ts --type tsx -B 2 -A 5Repository: QueueLab/QCX
Length of output: 83
🏁 Script executed:
#!/bin/bash
# Read the map-schemas.ts file to see MapStateFeedback definition
cat lib/types/map-schemas.tsRepository: QueueLab/QCX
Length of output: 6885
🏁 Script executed:
#!/bin/bash
# Search for MapStateFeedback definition across the codebase
rg "MapStateFeedback" -B 2 -A 10Repository: QueueLab/QCX
Length of output: 14276
🏁 Script executed:
#!/bin/bash
# Search for MapFeedback definition and usage
rg "MapFeedback" -B 2 -A 5Repository: QueueLab/QCX
Length of output: 1634
Replace the local MapFeedback interface with the centralized MapStateFeedback type from lib/types/map-schemas.
The codebase already defines MapStateFeedback as a centralized, schema-validated type used throughout the orchestrator, feedback analyzer, and context layer. Defining a duplicate MapFeedback interface here creates type inconsistency and maintenance overhead. While MapStateFeedback makes several fields optional (which differs from the local interface), the code already provides values for all fields, so this is compatible.
Import MapStateFeedback and remove the local interface definition.
🤖 Prompt for AI Agents
In @components/map/mapbox-map.tsx around lines 19 - 28, Replace the local
MapFeedback interface with the centralized MapStateFeedback type: remove the
MapFeedback interface declaration in mapbox-map.tsx, add an import for
MapStateFeedback from "lib/types/map-schemas", and update any usages or
annotations that reference MapFeedback to use MapStateFeedback instead (no code
logic changes needed since all fields are provided where used).
| // Effect to handle GeoJSON data updates | ||
| useEffect(() => { | ||
| if (!map.current) return; | ||
|
|
||
| const mapInstance = map.current; | ||
| const source = mapInstance.getSource('geojson-data'); | ||
|
|
||
| // If GeoJSON data is present, add or update the source and layers | ||
| if (mapData.geojson) { | ||
| const geojsonData = mapData.geojson as FeatureCollection; | ||
| if (source) { | ||
| (source as mapboxgl.GeoJSONSource).setData(geojsonData); | ||
| } else { | ||
| mapInstance.addSource('geojson-data', { | ||
| type: 'geojson', | ||
| data: geojsonData, | ||
| }); | ||
|
|
||
| // Add layer for points | ||
| mapInstance.addLayer({ | ||
| id: 'geojson-points', | ||
| type: 'circle', | ||
| source: 'geojson-data', | ||
| paint: { | ||
| 'circle-radius': 8, | ||
| 'circle-color': '#007cbf', | ||
| 'circle-stroke-width': 2, | ||
| 'circle-stroke-color': '#ffffff', | ||
| }, | ||
| filter: ['==', '$type', 'Point'], | ||
| }); | ||
|
|
||
| // Add layer for lines | ||
| mapInstance.addLayer({ | ||
| id: 'geojson-lines', | ||
| type: 'line', | ||
| source: 'geojson-data', | ||
| paint: { | ||
| 'line-color': '#ff4500', | ||
| 'line-width': 3, | ||
| }, | ||
| filter: ['==', '$type', 'LineString'], | ||
| }); | ||
| } | ||
| } else { | ||
| // If no GeoJSON data, remove layers and source if they exist | ||
| if (mapInstance.getLayer('geojson-points')) mapInstance.removeLayer('geojson-points'); | ||
| if (mapInstance.getLayer('geojson-lines')) mapInstance.removeLayer('geojson-lines'); | ||
| if (source) mapInstance.removeSource('geojson-data'); | ||
| } | ||
| }, [mapData.geojson]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing style-loaded check and polygon layer support.
Two issues with this effect:
-
Race condition risk: Adding sources/layers without checking
map.isStyleLoaded()can throw errors if the style hasn't loaded yet. Thegeojson-layer.tsxcomponent (from relevant snippets) properly handles this with anisStyleLoaded()check. -
Missing polygon support: The existing
GeoJsonLayercomponent handles polygons with fill and outline layers, but this implementation only handles points and lines.
Proposed fix with style-loaded check and polygon support
// Effect to handle GeoJSON data updates
useEffect(() => {
if (!map.current) return;
const mapInstance = map.current;
- const source = mapInstance.getSource('geojson-data');
- // If GeoJSON data is present, add or update the source and layers
- if (mapData.geojson) {
- const geojsonData = mapData.geojson as FeatureCollection;
- if (source) {
- (source as mapboxgl.GeoJSONSource).setData(geojsonData);
- } else {
- mapInstance.addSource('geojson-data', {
- type: 'geojson',
- data: geojsonData,
- });
-
- // Add layer for points
- mapInstance.addLayer({
+ const addGeoJSONLayers = () => {
+ const source = mapInstance.getSource('geojson-data');
+ if (mapData.geojson) {
+ const geojsonData = mapData.geojson as FeatureCollection;
+ if (source) {
+ (source as mapboxgl.GeoJSONSource).setData(geojsonData);
+ } else {
+ mapInstance.addSource('geojson-data', {
+ type: 'geojson',
+ data: geojsonData,
+ });
+
+ // Add layer for points
+ mapInstance.addLayer({
id: 'geojson-points',
type: 'circle',
source: 'geojson-data',
paint: {
'circle-radius': 8,
'circle-color': '#007cbf',
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff',
},
filter: ['==', '$type', 'Point'],
- });
+ });
- // Add layer for lines
- mapInstance.addLayer({
+ // Add layer for lines
+ mapInstance.addLayer({
id: 'geojson-lines',
type: 'line',
source: 'geojson-data',
paint: {
'line-color': '#ff4500',
'line-width': 3,
},
filter: ['==', '$type', 'LineString'],
- });
+ });
+
+ // Add layer for polygons
+ mapInstance.addLayer({
+ id: 'geojson-polygons',
+ type: 'fill',
+ source: 'geojson-data',
+ paint: {
+ 'fill-color': '#088',
+ 'fill-opacity': 0.4,
+ },
+ filter: ['==', '$type', 'Polygon'],
+ });
+ }
+ } else {
+ // Remove layers and source if they exist
+ if (mapInstance.getLayer('geojson-points')) mapInstance.removeLayer('geojson-points');
+ if (mapInstance.getLayer('geojson-lines')) mapInstance.removeLayer('geojson-lines');
+ if (mapInstance.getLayer('geojson-polygons')) mapInstance.removeLayer('geojson-polygons');
+ if (source) mapInstance.removeSource('geojson-data');
}
- } else {
- // If no GeoJSON data, remove layers and source if they exist
- if (mapInstance.getLayer('geojson-points')) mapInstance.removeLayer('geojson-points');
- if (mapInstance.getLayer('geojson-lines')) mapInstance.removeLayer('geojson-lines');
- if (source) mapInstance.removeSource('geojson-data');
+ };
+
+ if (mapInstance.isStyleLoaded()) {
+ addGeoJSONLayers();
+ } else {
+ mapInstance.once('style.load', addGeoJSONLayers);
}
}, [mapData.geojson]);📝 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.
| // Effect to handle GeoJSON data updates | |
| useEffect(() => { | |
| if (!map.current) return; | |
| const mapInstance = map.current; | |
| const source = mapInstance.getSource('geojson-data'); | |
| // If GeoJSON data is present, add or update the source and layers | |
| if (mapData.geojson) { | |
| const geojsonData = mapData.geojson as FeatureCollection; | |
| if (source) { | |
| (source as mapboxgl.GeoJSONSource).setData(geojsonData); | |
| } else { | |
| mapInstance.addSource('geojson-data', { | |
| type: 'geojson', | |
| data: geojsonData, | |
| }); | |
| // Add layer for points | |
| mapInstance.addLayer({ | |
| id: 'geojson-points', | |
| type: 'circle', | |
| source: 'geojson-data', | |
| paint: { | |
| 'circle-radius': 8, | |
| 'circle-color': '#007cbf', | |
| 'circle-stroke-width': 2, | |
| 'circle-stroke-color': '#ffffff', | |
| }, | |
| filter: ['==', '$type', 'Point'], | |
| }); | |
| // Add layer for lines | |
| mapInstance.addLayer({ | |
| id: 'geojson-lines', | |
| type: 'line', | |
| source: 'geojson-data', | |
| paint: { | |
| 'line-color': '#ff4500', | |
| 'line-width': 3, | |
| }, | |
| filter: ['==', '$type', 'LineString'], | |
| }); | |
| } | |
| } else { | |
| // If no GeoJSON data, remove layers and source if they exist | |
| if (mapInstance.getLayer('geojson-points')) mapInstance.removeLayer('geojson-points'); | |
| if (mapInstance.getLayer('geojson-lines')) mapInstance.removeLayer('geojson-lines'); | |
| if (source) mapInstance.removeSource('geojson-data'); | |
| } | |
| }, [mapData.geojson]); | |
| // Effect to handle GeoJSON data updates | |
| useEffect(() => { | |
| if (!map.current) return; | |
| const mapInstance = map.current; | |
| const addGeoJSONLayers = () => { | |
| const source = mapInstance.getSource('geojson-data'); | |
| if (mapData.geojson) { | |
| const geojsonData = mapData.geojson as FeatureCollection; | |
| if (source) { | |
| (source as mapboxgl.GeoJSONSource).setData(geojsonData); | |
| } else { | |
| mapInstance.addSource('geojson-data', { | |
| type: 'geojson', | |
| data: geojsonData, | |
| }); | |
| // Add layer for points | |
| mapInstance.addLayer({ | |
| id: 'geojson-points', | |
| type: 'circle', | |
| source: 'geojson-data', | |
| paint: { | |
| 'circle-radius': 8, | |
| 'circle-color': '#007cbf', | |
| 'circle-stroke-width': 2, | |
| 'circle-stroke-color': '#ffffff', | |
| }, | |
| filter: ['==', '$type', 'Point'], | |
| }); | |
| // Add layer for lines | |
| mapInstance.addLayer({ | |
| id: 'geojson-lines', | |
| type: 'line', | |
| source: 'geojson-data', | |
| paint: { | |
| 'line-color': '#ff4500', | |
| 'line-width': 3, | |
| }, | |
| filter: ['==', '$type', 'LineString'], | |
| }); | |
| // Add layer for polygons | |
| mapInstance.addLayer({ | |
| id: 'geojson-polygons', | |
| type: 'fill', | |
| source: 'geojson-data', | |
| paint: { | |
| 'fill-color': '#088', | |
| 'fill-opacity': 0.4, | |
| }, | |
| filter: ['==', '$type', 'Polygon'], | |
| }); | |
| } | |
| } else { | |
| // Remove layers and source if they exist | |
| if (mapInstance.getLayer('geojson-points')) mapInstance.removeLayer('geojson-points'); | |
| if (mapInstance.getLayer('geojson-lines')) mapInstance.removeLayer('geojson-lines'); | |
| if (mapInstance.getLayer('geojson-polygons')) mapInstance.removeLayer('geojson-polygons'); | |
| if (source) mapInstance.removeSource('geojson-data'); | |
| } | |
| }; | |
| if (mapInstance.isStyleLoaded()) { | |
| addGeoJSONLayers(); | |
| } else { | |
| mapInstance.once('style.load', addGeoJSONLayers); | |
| } | |
| }, [mapData.geojson]); |
🤖 Prompt for AI Agents
In @components/map/mapbox-map.tsx around lines 559 - 609, The effect that
updates the 'geojson-data' source must guard against style not being loaded and
also add polygon support: check map.current and use mapInstance.isStyleLoaded()
(or listen for the 'styledata' event) before calling addSource/addLayer to avoid
race errors, and if waiting for style, retry or attach a one-time listener; when
adding layers, add two polygon layers (e.g., 'geojson-polygons-fill' with type
'fill' and appropriate 'fill-color'/'fill-opacity' and
'geojson-polygons-outline' with type 'line' for stroke) and use filters for
'Polygon' and 'MultiPolygon'; also ensure you remove those polygon layers when
mapData.geojson is falsy and when updating source, mirror existing logic for
points and lines using the same source id 'geojson-data'.
| case 'fitBounds': | ||
| if (command.params.bounds) { | ||
| const { bounds, padding, duration, ...otherOptions } = command.params; | ||
| const fitBoundsOptions: any = {}; | ||
| if (padding !== undefined) fitBoundsOptions.padding = padding; | ||
| if (duration !== undefined) fitBoundsOptions.duration = duration; | ||
| Object.assign(fitBoundsOptions, otherOptions); | ||
| mapInstance.fitBounds(bounds, fitBoundsOptions); | ||
| } | ||
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Minor: fitBoundsOptions is typed as any, losing type safety.
Consider using mapboxgl.FitBoundsOptions for better type checking and IDE support.
- const fitBoundsOptions: any = {};
+ const fitBoundsOptions: mapboxgl.FitBoundsOptions = {};📝 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.
| case 'fitBounds': | |
| if (command.params.bounds) { | |
| const { bounds, padding, duration, ...otherOptions } = command.params; | |
| const fitBoundsOptions: any = {}; | |
| if (padding !== undefined) fitBoundsOptions.padding = padding; | |
| if (duration !== undefined) fitBoundsOptions.duration = duration; | |
| Object.assign(fitBoundsOptions, otherOptions); | |
| mapInstance.fitBounds(bounds, fitBoundsOptions); | |
| } | |
| break; | |
| case 'fitBounds': | |
| if (command.params.bounds) { | |
| const { bounds, padding, duration, ...otherOptions } = command.params; | |
| const fitBoundsOptions: mapboxgl.FitBoundsOptions = {}; | |
| if (padding !== undefined) fitBoundsOptions.padding = padding; | |
| if (duration !== undefined) fitBoundsOptions.duration = duration; | |
| Object.assign(fitBoundsOptions, otherOptions); | |
| mapInstance.fitBounds(bounds, fitBoundsOptions); | |
| } | |
| break; |
🤖 Prompt for AI Agents
In @components/map/mapbox-map.tsx around lines 642 - 651, The fitBoundsOptions
variable is currently typed as any which loses type safety; change its
declaration to use mapboxgl.FitBoundsOptions (e.g., initialize as {} as
mapboxgl.FitBoundsOptions) and keep assigning padding/duration/otherOptions into
it before calling mapInstance.fitBounds(bounds, fitBoundsOptions); ensure
mapboxgl is available in scope (import or reference the global type) so the
compiler recognizes mapboxgl.FitBoundsOptions and you get IDE/type checking for
the fitBounds call.
| // Guard flag to ensure feedbackHandler only runs once | ||
| let handled = false; | ||
| let fallbackTimeoutId: NodeJS.Timeout | null = null; | ||
|
|
||
| // Capture feedback after commands complete | ||
| const feedbackHandler = () => { | ||
| if (handled) return; | ||
| handled = true; | ||
|
|
||
| if (fallbackTimeoutId) { | ||
| clearTimeout(fallbackTimeoutId); | ||
| } | ||
|
|
||
| // Clean up event listener | ||
| mapInstance.off('moveend', feedbackHandler); | ||
|
|
||
| const center = mapInstance.getCenter(); | ||
| const bounds = mapInstance.getBounds(); | ||
|
|
||
| const feedback: MapFeedback = { | ||
| success: !executionError, | ||
| currentCenter: [center.lng, center.lat], | ||
| currentZoom: mapInstance.getZoom(), | ||
| currentPitch: mapInstance.getPitch(), | ||
| currentBearing: mapInstance.getBearing(), | ||
| error: executionError, | ||
| timestamp: Date.now(), | ||
| }; | ||
|
|
||
| if (bounds) { | ||
| feedback.currentBounds = [ | ||
| [bounds.getWest(), bounds.getSouth()], | ||
| [bounds.getEast(), bounds.getNorth()] | ||
| ]; | ||
| } | ||
|
|
||
| if (isDebug) console.log('📍 Map feedback:', feedback); | ||
|
|
||
| // Send feedback via callback if provided | ||
| if (mapData.feedbackCallback) { | ||
| mapData.feedbackCallback(feedback); | ||
| } | ||
|
|
||
| // Store feedback in context | ||
| setMapData(prev => ({ | ||
| ...prev, | ||
| mapStateFeedback: feedback, | ||
| mapCommands: null, // Clear commands after execution | ||
| })); | ||
| }; | ||
|
|
||
| // Wait for map to finish moving | ||
| mapInstance.once('moveend', feedbackHandler); | ||
|
|
||
| // Fallback timeout in case moveend doesn't fire | ||
| // Increased timeout to allow for animation start | ||
| fallbackTimeoutId = setTimeout(() => { | ||
| if (mapInstance.isMoving && mapInstance.isMoving()) { | ||
| return; // Still moving, wait for moveend | ||
| } | ||
| // If not moving, trigger feedback immediately | ||
| feedbackHandler(); | ||
| }, 500); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Memory leak: Missing cleanup for timeout and event listener.
The fallbackTimeoutId timeout and moveend event listener are not cleaned up when the effect re-runs or unmounts. This can cause memory leaks and stale callbacks executing on an unmounted component.
Proposed fix with proper cleanup
// Effect to execute map commands with feedback
useEffect(() => {
+ let fallbackTimeoutId: NodeJS.Timeout | null = null;
+ let handled = false;
+
if (isDebug) {
console.log('🗺️ Map commands useEffect triggered:', {
// ...
});
}
if (!map.current || !mapData.mapCommands || mapData.mapCommands.length === 0) {
if (isDebug) console.log('⚠️ Skipping command execution - missing map or commands');
return;
}
const mapInstance = map.current;
let executionError: string | undefined;
// ... command execution code ...
try {
// ... forEach loop ...
- // Guard flag to ensure feedbackHandler only runs once
- let handled = false;
- let fallbackTimeoutId: NodeJS.Timeout | null = null;
// Capture feedback after commands complete
const feedbackHandler = () => {
// ... existing handler code ...
};
// Wait for map to finish moving
mapInstance.once('moveend', feedbackHandler);
// Fallback timeout
fallbackTimeoutId = setTimeout(() => {
// ... existing timeout code ...
}, 500);
} catch (error) {
// ... error handling ...
}
+ // Cleanup function
+ return () => {
+ if (fallbackTimeoutId) {
+ clearTimeout(fallbackTimeoutId);
+ }
+ if (!handled) {
+ mapInstance.off('moveend', feedbackHandler);
+ }
+ };
}, [mapData.mapCommands, mapData.feedbackCallback, setMapData, isDebug]);Note: The feedbackHandler function needs to be declared outside the try block for the cleanup to reference it properly, or use a ref to store it.
🤖 Prompt for AI Agents
In @components/map/mapbox-map.tsx around lines 677 - 739, The effect can leak
because fallbackTimeoutId and the moveend listener referencing feedbackHandler
aren't cleaned up on re-run/unmount; declare feedbackHandler and
fallbackTimeoutId in stable scope (or store them in refs) so the cleanup can
access the same references, and in the effect return
clearTimeout(fallbackTimeoutId) and remove the listener via
mapInstance.off('moveend', feedbackHandler) (also set handled = true or similar
guard) to ensure the timeout and event listener are always removed; adjust
placement so feedbackHandler is defined outside try/async blocks or use useRef
to hold the handler before registering mapInstance.once.
|
@coderabbitai Make sure you make the map language controlled via composio to enable location rendering. Give examples of queries that should work perfectly |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain@ngoiyaeric, I'll analyze the current state and plan the code changes needed to make the map fully language-controlled via Composio. Let me check the integration points first. 🏁 Script executed: Length of output: 10226 [plan_code_changes] Perfect! I can see the enricher IS being called at line 389, but WITHOUT the Composio client. The orchestrator (which has full MCP integration) is imported but never used. Let me plan the code changes to enable full language-controlled map rendering via Composio. Implementation Plan: Enable Composio-Powered Map ControlCurrent Flow (Limited):Fixed Flow (Composio-Powered):🎯 Example Queries That Will Work PerfectlyAfter these changes, the following queries will work with accurate Composio-powered geocoding: ✅ Single Location Queries
✅ Multiple Location Queries
✅ Route & Distance Queries
✅ Search & Discovery Queries
✅ Advanced 3D View Queries
✅ Reverse Geocoding
🚀 What Makes This WorkBefore (Current):❌ LLM hallucinates coordinates from text After (With Composio):✅ Accurate geocoding via Composio SDK 📊 Expected Performance
All queries will have accurate, real-world coordinates from Composio instead of LLM-hallucinated data! 🎯 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/actions.tsx (1)
644-654: Wrap switch case declaration in a block.The static analysis tool (Biome) correctly identifies that declarations in switch cases can leak to other cases, potentially causing unexpected behavior.
🐛 Proposed fix: wrap in block scope
- case 'related': - const relatedQueries = createStreamableValue<PartialRelated>() - relatedQueries.done(JSON.parse(content as string)) - return { - id, - component: ( - <Section title="Related" separator={true}> - <SearchRelated relatedQueries={relatedQueries.value} /> - </Section> - ) - } + case 'related': { + const relatedQueries = createStreamableValue<PartialRelated>() + relatedQueries.done(JSON.parse(content as string)) + return { + id, + component: ( + <Section title="Related" separator={true}> + <SearchRelated relatedQueries={relatedQueries.value} /> + </Section> + ) + } + }
🤖 Fix all issues with AI agents
In @lib/agents/map-control-orchestrator.tsx:
- Around line 43-48: The OrchestratorOptions currently types mcpClient as any,
losing type safety; import the proper client type from the Composio SDK (e.g.,
ComposioClient or whatever exported client type) and replace mcpClient?: any
with mcpClient?: ComposioClient (or the SDK type), or if the SDK type is
unavailable create a local interface (e.g., MpcClientLike) describing the
methods you use and use that type; update all references to
OrchestratorOptions/mcpClient to use the new type so you regain IDE completions
and compile-time checks.
- Around line 110-136: The code overwrites geocoded features because
this.state.geojson is set from parserOutput.geojson after calling
geocodeLocations; move the assignment this.state.geojson = parserOutput.geojson
to before calling this.geocodeLocations(parserOutput.extractedLocations), and
ensure geocodeLocations either mutates this.state.geojson or returns merged
geocoded features to be merged into this.state.geojson (update the call to use
the returned value if it returns features). After that, run
validateGeoJSON(this.state.geojson) as before to validate the final merged
GeoJSON.
- Around line 91-94: The process method on MapControlOrchestrator declares a
feedbackCallback parameter but never invokes it; either remove the parameter or
call feedbackCallback with a MapStateFeedback when processing completes or at
key iteration points. Locate the async process(researcherResponse,
feedbackCallback?) method and, if keeping the callback, construct and pass a
MapStateFeedback object (e.g., final status, updated map state, progress or
error) to feedbackCallback(...) before returning the LocationResponse (and
optionally at intermediate steps), or delete the feedbackCallback parameter and
all related references to eliminate the unused argument.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
app/actions.tsxlib/agents/map-control-orchestrator.tsx
🧰 Additional context used
🪛 Biome (2.1.2)
app/actions.tsx
[error] 645-646: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (12)
lib/agents/map-control-orchestrator.tsx (6)
1-41: LGTM!The imports and classification prompt are well-structured. The prompt provides clear classification types, complexity levels, and MCP operations guidance.
63-86: LGTM!The constructor properly initializes defaults and state. The pattern of merging user-provided options with defaults is clean.
147-160: LGTM!Command validation with filtering of invalid commands is a good defensive pattern. The filtering relies on error field naming convention, which works but could be more robust with structured error indices.
279-300: LGTM!The classification method has proper error handling with a conservative fallback that defaults to
simple_locationwithrequiresMCP: false, ensuring the system degrades gracefully.
366-440: LGTM!The
geocodeLocationsmethod handles multiple Composio response formats robustly and correctly merges geocoded features into the existing GeoJSON state.
446-452: LGTM!The convenience helper function provides a clean API for simple usage without managing orchestrator instances.
app/actions.tsx (6)
16-33: LGTM!The new imports are properly organized and all appear to be used in the implementation.
72-72: LGTM!Storing multimodal content as a JSON string ensures proper serialization for the AI state.
290-290: Type assertion without runtime validation.The cast
as PartialInquiryassumesinquire()returns a compatible structure. Consider adding a runtime type guard if the return type isn't guaranteed.
427-457: LGTM!The AI state update correctly stores the
LocationResponseas a tool message, enabling proper UI rendering ingetUIStateFromAIState. UsinglocationResponse.textfor the assistant content maintains consistency with the enriched response.
703-722: LGTM!The validation logic correctly checks the
LocationResponsestructure before rendering. The guard ensurestextis a string andgeojsonis either null or a valid FeatureCollection shape, preventing runtime errors inLocationResponseHandler.
376-416: Robust fallback chain for map enrichment.The implementation has a solid three-tier fallback:
- Full orchestrator with MCP
- Basic enricher v2
- Minimal response with null geojson
The environment variable check at line 383 prevents unnecessary initialization attempts. The
initializeComposioMapboxfunction correctly returns an object with bothcomposioandconnectionIdproperties as expected by the consumer code.
|
@coderabbitai 15:54:20.087 Running build in Portland, USA (West) – pdx1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In @lib/agents/map-control-orchestrator.tsx:
- Around line 328-349: classifyQuery uses this.model which is a Promise, causing
generateObject to receive an unresolved promise; before calling generateObject
inside classifyQuery await the model (e.g., const model = await this.model) and
pass that resolved model to generateObject so generateObject gets the actual
model instance; update references in classifyQuery to use the resolved model
variable.
- Around line 380-400: The placeholders for 'calculate_distance' and
'generate_map_link' in MapControlOrchestrator mean queries classified with
requiresMCP: true and mcpOperations including 'calculate_distance' won't perform
routing; either implement calculate_distance (use
this.options.mcpClient.executeAction with a real MCP action such as
'mapbox_directions' or 'mapbox_matrix', pass origin/destination coords, profile,
and parse distance/duration into result) and implement generate_map_link
(construct a Mapbox/static or directions URL from coords and include it in
result), or change classifyQuery so it doesn't mark route_distance queries as
requiresMCP or include 'calculate_distance' in mcpOperations; update the code
paths referencing calculate_distance and generate_map_link in
map-control-orchestrator (and any tests) accordingly so the classifier and
executor stay consistent.
- Around line 173-186: The current filtering of this.state.commands is brittle
because it parses validation.errors field names (commands[${idx}]) from
validateMapCommands; change validateMapCommands to return explicit invalid
command indices (e.g., invalidIndices: number[]) or a per-command boolean array,
and update the call in map-control-orchestrator to use that property when
filtering (use validation.invalidIndices to remove invalid commands and preserve
others); also keep logging validation.errors/validation.warnings but stop
depending on string parsing of error.field in this.state.commands filtering
logic.
- Around line 52-59: OrchestratorState currently types mcpData as any[], which
loses type safety; define a new interface (e.g., MCPResult or McpDataItem) that
models the MCP result shape (fields returned/consumed by map-control logic) and
replace mcpData: any[] with mcpData: MCPResult[] in the OrchestratorState
definition, then update any functions, variables, and places that construct or
read mcpData (search for usages of mcpData and MapControlOrchestrator-related
handlers) to use the new type and adjust property accesses as needed to satisfy
the compiler.
- Around line 65-96: getModel() is async but the constructor assigns it
synchronously to this.model, so this.model ends up as a Promise and breaks
classifyQuery; change the initialization to resolve the model asynchronously
(e.g., add a static async MapControlOrchestrator.create(options) or an async
init() that awaits getModel() and sets this.model) and ensure classifyQuery uses
the resolved model (or awaits a ready promise) before calling generateObject;
also fix types by making OrchestratorOptions.mcpClient nullable (Composio |
null) and stop forcing Required<OrchestratorOptions> with as any casts so the
constructor can accept and explicitly set mcpClient = null when absent.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
lib/agents/map-control-orchestrator.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
lib/agents/map-control-orchestrator.tsx (5)
lib/types/map-schemas.ts (7)
QueryClassification(222-222)GeoJSONFeatureCollection(214-214)MapCommand(215-215)MapStateFeedback(217-217)LocationResponse(223-223)QueryClassificationSchema(179-196)GeoJSONFeature(213-213)lib/utils/index.ts (1)
getModel(20-121)lib/agents/map-workers/geojson-parser.tsx (1)
geojsonParser(72-111)lib/agents/map-workers/map-command-generator.tsx (1)
mapCommandGenerator(73-155)lib/agents/map-workers/feedback-analyzer.tsx (1)
feedbackAnalyzer(50-138)
🔇 Additional comments (5)
lib/agents/map-control-orchestrator.tsx (5)
1-16: Imports look correct.The imports are properly structured, pulling from the correct schema files and worker modules. The
Composiotype import from@composio/corealigns with the library context.
18-43: Prompt aligns with schema definitions.The classification types and MCP operations match the
QueryClassificationSchemaenums. Clear and well-structured.
248-323: Feedback processing logic is well-structured.The iteration limit check and action-based switch handling are appropriate. The logging provides good observability for debugging.
419-474: Geocoding implementation handles multiple response formats well.The method correctly handles both
data.featuresanddata.locationresponse structures from the Composio API. Error handling per location prevents one failure from stopping all geocoding.
203-212: Initial feedback notification is intentional; full map state is sent by the Mapbox component.The minimal
MapStateFeedbacksent at lines 205–209 is a status notification that initial processing completed—not the actual map state feedback. The complete feedback withcurrentCenter,currentZoom, andcurrentBoundsis sent by themapbox-map.tsxcomponent after executing commands (see mapbox-map.tsx:716–717). The feedback loop is two-stage: orchestrator sends an initial success/error status, then the map component sends full state data whenmoveendfires. This design is correct.Consider adding a comment clarifying that this is an initial status notification, and the full map state feedback is populated by the Mapbox component to make the two-stage flow explicit.
| export class MapControlOrchestrator { | ||
| private options: Required<OrchestratorOptions>; | ||
| private state: OrchestratorState; | ||
| private model: any; | ||
|
|
||
| constructor(options: OrchestratorOptions = {}) { | ||
| this.options = { | ||
| maxIterations: options.maxIterations || 3, | ||
| enableFeedbackLoop: options.enableFeedbackLoop ?? true, | ||
| mcpClient: options.mcpClient as Composio, // Casting to Required<T> type but it can be null internally if passed as null. | ||
| // Actually strictly speaking Required<OrchestratorOptions> would require mcpClient to be Composio. | ||
| // But we default it to null in constructor options. | ||
| // Let's adjust strict typing logic. | ||
| connectionId: options.connectionId || '', | ||
| } as any; // Cast to bypass strict null checks for optional fields in Required | ||
|
|
||
| // Handle null explicitly | ||
| if (!options.mcpClient) { | ||
| (this.options as any).mcpClient = null; | ||
| } | ||
|
|
||
| this.state = { | ||
| iteration: 0, | ||
| classification: null, | ||
| geojson: null, | ||
| commands: [], | ||
| mcpData: [], | ||
| feedback: null, | ||
| }; | ||
|
|
||
| this.model = getModel(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: getModel() is async but not awaited.
getModel() returns a Promise (see lib/utils/index.ts lines 19-120), but line 95 assigns it synchronously. This means this.model holds a Promise, not the resolved model, causing generateObject to fail at runtime in classifyQuery.
Additionally, the as any casts on lines 74-79 and 83 indicate a type design issue. Consider making mcpClient explicitly nullable instead of using Required<T> with type escapes.
🐛 Proposed fix
export class MapControlOrchestrator {
- private options: Required<OrchestratorOptions>;
+ private options: {
+ maxIterations: number;
+ enableFeedbackLoop: boolean;
+ mcpClient: Composio | null;
+ connectionId: string;
+ };
private state: OrchestratorState;
private model: any;
+ private modelPromise: Promise<any>;
constructor(options: OrchestratorOptions = {}) {
this.options = {
maxIterations: options.maxIterations || 3,
enableFeedbackLoop: options.enableFeedbackLoop ?? true,
- mcpClient: options.mcpClient as Composio,
- connectionId: options.connectionId || '',
- } as any;
-
- if (!options.mcpClient) {
- (this.options as any).mcpClient = null;
- }
+ mcpClient: options.mcpClient || null,
+ connectionId: options.connectionId || '',
+ };
this.state = {
iteration: 0,
classification: null,
geojson: null,
commands: [],
mcpData: [],
feedback: null,
};
- this.model = getModel();
+ this.modelPromise = getModel();
+ }
+
+ private async ensureModel(): Promise<any> {
+ if (!this.model) {
+ this.model = await this.modelPromise;
+ }
+ return this.model;
}Then update classifyQuery to use the resolved model:
private async classifyQuery(text: string): Promise<QueryClassification> {
try {
+ const model = await this.ensureModel();
const { object } = await generateObject({
- model: this.model,
+ model,
schema: QueryClassificationSchema,🤖 Prompt for AI Agents
In @lib/agents/map-control-orchestrator.tsx around lines 65 - 96, getModel() is
async but the constructor assigns it synchronously to this.model, so this.model
ends up as a Promise and breaks classifyQuery; change the initialization to
resolve the model asynchronously (e.g., add a static async
MapControlOrchestrator.create(options) or an async init() that awaits getModel()
and sets this.model) and ensure classifyQuery uses the resolved model (or awaits
a ready promise) before calling generateObject; also fix types by making
OrchestratorOptions.mcpClient nullable (Composio | null) and stop forcing
Required<OrchestratorOptions> with as any casts so the constructor can accept
and explicitly set mcpClient = null when absent.
| case 'calculate_distance': | ||
| // Placeholder for matrix/directions | ||
| break; | ||
|
|
||
| case 'search_nearby': | ||
| // Placeholder for search | ||
| if (this.options.connectionId) { | ||
| result = await this.options.mcpClient.executeAction({ | ||
| action: 'mapbox_search_box_text_search', | ||
| params: { | ||
| q: text | ||
| }, | ||
| connectedAccountId: this.options.connectionId | ||
| }); | ||
| } | ||
| break; | ||
|
|
||
| case 'generate_map_link': | ||
| // Would need coordinates | ||
| // Skipping for now | ||
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Placeholder MCP operations may leave functionality incomplete.
calculate_distance (line 381) and generate_map_link (line 398) are placeholders. If route_distance queries classify with requiresMCP: true and mcpOperations: ['calculate_distance'], no actual routing occurs.
Consider either implementing these or updating classifyQuery to avoid suggesting unsupported operations.
Would you like me to help implement the calculate_distance operation using Composio's matrix or directions API?
🤖 Prompt for AI Agents
In @lib/agents/map-control-orchestrator.tsx around lines 380 - 400, The
placeholders for 'calculate_distance' and 'generate_map_link' in
MapControlOrchestrator mean queries classified with requiresMCP: true and
mcpOperations including 'calculate_distance' won't perform routing; either
implement calculate_distance (use this.options.mcpClient.executeAction with a
real MCP action such as 'mapbox_directions' or 'mapbox_matrix', pass
origin/destination coords, profile, and parse distance/duration into result) and
implement generate_map_link (construct a Mapbox/static or directions URL from
coords and include it in result), or change classifyQuery so it doesn't mark
route_distance queries as requiresMCP or include 'calculate_distance' in
mcpOperations; update the code paths referencing calculate_distance and
generate_map_link in map-control-orchestrator (and any tests) accordingly so the
classifier and executor stay consistent.
| export async function mapControlOrchestrator( | ||
| researcherResponse: string, | ||
| options?: OrchestratorOptions | ||
| ): Promise<LocationResponse> { | ||
| const orchestrator = new MapControlOrchestrator(options); | ||
| return orchestrator.process(researcherResponse); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Convenience function omits feedbackCallback parameter.
The exported mapControlOrchestrator function doesn't pass a feedbackCallback to process(), so callers using this helper cannot receive feedback. Consider extending the options or signature to support feedback.
♻️ Suggested improvement
+interface MapControlOrchestratorOptions extends OrchestratorOptions {
+ feedbackCallback?: (feedback: MapStateFeedback) => void;
+}
+
export async function mapControlOrchestrator(
researcherResponse: string,
- options?: OrchestratorOptions
+ options?: MapControlOrchestratorOptions
): Promise<LocationResponse> {
const orchestrator = new MapControlOrchestrator(options);
- return orchestrator.process(researcherResponse);
+ return orchestrator.process(researcherResponse, options?.feedbackCallback);
}📝 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.
| export async function mapControlOrchestrator( | |
| researcherResponse: string, | |
| options?: OrchestratorOptions | |
| ): Promise<LocationResponse> { | |
| const orchestrator = new MapControlOrchestrator(options); | |
| return orchestrator.process(researcherResponse); | |
| } | |
| interface MapControlOrchestratorOptions extends OrchestratorOptions { | |
| feedbackCallback?: (feedback: MapStateFeedback) => void; | |
| } | |
| export async function mapControlOrchestrator( | |
| researcherResponse: string, | |
| options?: MapControlOrchestratorOptions | |
| ): Promise<LocationResponse> { | |
| const orchestrator = new MapControlOrchestrator(options); | |
| return orchestrator.process(researcherResponse, options?.feedbackCallback); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/actions.tsx (1)
645-655: Wrap switch case declaration in a block.The
relatedQueriesconstant declaration inside the'related'case can leak to other switch clauses. Static analysis correctly flags this.Proposed fix
- case 'related': - const relatedQueries = createStreamableValue<PartialRelated>() - relatedQueries.done(JSON.parse(content as string)) - return { - id, - component: ( - <Section title="Related" separator={true}> - <SearchRelated relatedQueries={relatedQueries.value} /> - </Section> - ) - } + case 'related': { + const relatedQueries = createStreamableValue<PartialRelated>() + relatedQueries.done(JSON.parse(content as string)) + return { + id, + component: ( + <Section title="Related" separator={true}> + <SearchRelated relatedQueries={relatedQueries.value} /> + </Section> + ), + } + }
🤖 Fix all issues with AI agents
In @lib/agents/map-control-orchestrator.tsx:
- Around line 423-425: The current cast of result to any (resAny) hides the real
Composio tool response shape; define a proper type/interface (e.g.,
ComposioExecutionResult) that models the expected fields (like data, error,
etc.), replace the any assertion with a narrow cast to that type, and extract
data via const data = (result as ComposioExecutionResult).data ?? result to
preserve compatibility; update any related helpers/types and adjust consumers of
data accordingly so type-checking enforces the Composio response structure
instead of using resAny.
- Around line 381-389: The 'generate_map_link' switch case declares a const link
which can leak into other cases; wrap the case body in its own block scope (add
{ ... } after case 'generate_map_link': and before break) so the const link is
scoped locally and cannot conflict with other switch clauses — update the case
block that builds the Mapbox URL and assigns result accordingly (symbols: case
'generate_map_link', const link, result).
In @lib/agents/map-workers/validator.tsx:
- Around line 187-192: The zoom-range check is unreachable because
MapCommandParamsSchema enforces zoom via z.number().min(0).max(22); remove the
conditional block that inspects cmd.params.zoom and pushes the `Unusual zoom
level` warning (the if (cmd.params.zoom !== undefined) { if (cmd.params.zoom < 0
|| cmd.params.zoom > 22) { warnings.push(...) } } block) or replace it with a
different runtime check if you intend to warn about other conditions; ensure no
other code expects that warning to be produced when relying on Zod validation.
In @lib/types/map-schemas.ts:
- Around line 127-141: MapStateFeedbackSchema currently accepts raw numbers for
currentBounds and currentCenter; add explicit latitude/longitude range
validation to match other schemas by replacing z.number() inside the coordinate
tuples with z.number().min(-180).max(180) for longitude and
z.number().min(-90).max(90) for latitude (or the project’s chosen order) so both
currentCenter (z.tuple([lon, lat])) and each coordinate in currentBounds’ nested
tuples are validated; update MapStateFeedbackSchema to use those bounded
z.number() validators for all coordinate entries.
- Around line 98-99: The bearing schema currently uses
z.number().min(0).max(360).optional(), which wrongly rejects valid negative
bearings; update the bearing validator in map-schemas (the bearing field next to
pitch) to allow the full Mapbox range by changing its bounds to min(-180) and
max(360) (keeping .optional()), so negative bearings like -45 are accepted.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
app/actions.tsxlib/agents/map-control-orchestrator.tsxlib/agents/map-workers/validator.tsxlib/types/map-schemas.ts
🧰 Additional context used
🧬 Code graph analysis (1)
lib/agents/map-workers/validator.tsx (1)
lib/types/map-schemas.ts (5)
ValidationResult(229-229)GeoJSONFeatureCollectionSchema(86-89)GeoJSONFeatureCollection(222-222)MapCommandSchema(119-125)MapCommand(223-223)
🪛 Biome (2.1.2)
app/actions.tsx
[error] 646-647: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
lib/agents/map-control-orchestrator.tsx
[error] 384-386: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (4)
lib/agents/map-control-orchestrator.tsx (1)
66-89: LGTM on class structure and initialization.The orchestrator class properly handles default options, initializes state, and defers model resolution. The pattern of storing
getModel()as a promise and awaiting it inclassifyQuery()is correct.lib/agents/map-workers/validator.tsx (1)
228-262: LGTM on batch command validation.The
validateMapCommandsfunction properly aggregates per-command validation results, prefixes field paths with indices, and tracks invalid indices for downstream filtering.app/actions.tsx (2)
376-417: LGTM on orchestrator integration with proper fallback chain.The integration correctly:
- Checks for required environment variables before Composio initialization
- Handles Composio initialization failures gracefully
- Falls back to
geojsonEnricherV2if orchestration fails- Provides a minimal fallback if even the enricher fails
- Logs appropriately at each step
704-723: LGTM on defensive validation for geojsonEnrichment.Good defensive checks before rendering
LocationResponseHandler:
- Verifies
textis a string- Validates
geojsonis eithernullor a validFeatureCollection- Logs a warning and returns null for malformed data
| case 'calculate_distance': | ||
| // Implement calculate_distance | ||
| // Extract potential locations? This is hard without parsing "A to B" | ||
| // For now, we assume text contains enough info, or we skip if not structured. | ||
| // Using mapbox_directions is an option if we can parse waypoints. | ||
| // As a fallback/placeholder that fulfills the requirement: | ||
| console.log('Attempting calculate_distance via Mapbox Directions...'); | ||
| // We'd need to extract origin/dest. | ||
| // Let's try to just pass the text as query if tool supports it? | ||
| // mapbox_directions takes 'waypoints'. | ||
| // If we can't parse, we might skip. | ||
| // But prompt says "pass origin/destination coords...". The orchestrator doesn't have them yet unless from geocode. | ||
| // I'll leave a log and dummy result or try a broader search if applicable. | ||
| // ACTUALLY, I'll use mapbox_search_box_text_search as a proxy if I can't do directions, OR just skip. | ||
| // But the prompt wants it implemented. | ||
| // I will mock it for now or assume we extracted them. | ||
| // Since I can't easily parse "A to B" reliably without another LLM call or regex, I will skip with a log, | ||
| // BUT strictly I should use executeAction. | ||
| // I'll try to find if there is a 'natural_language_directions' tool? No. | ||
|
|
||
| // I will construct a Mapbox Directions URL as a result to satisfy "calculate_distance" somewhat? | ||
| // No, that's generate_map_link. | ||
|
|
||
| // I'll try to geocode the whole text and see if it gives route? Unlikely. | ||
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calculate_distance operation is unimplemented.
The calculate_distance case contains extensive comments explaining the difficulty but performs no actual operation. This results in silent failures when the orchestrator classifies a query as needing distance calculation.
At minimum, log a warning and consider returning a structured "not implemented" result, or implement the operation using mapbox_directions with parsed waypoints.
Suggested minimal fix
case 'calculate_distance':
- // ... extensive comments ...
- break;
+ console.warn('calculate_distance operation not yet implemented - skipping');
+ result = {
+ error: 'calculate_distance not implemented',
+ description: 'Route/distance calculation requires waypoint parsing - not yet supported'
+ };
+ break;📝 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.
| case 'calculate_distance': | |
| // Implement calculate_distance | |
| // Extract potential locations? This is hard without parsing "A to B" | |
| // For now, we assume text contains enough info, or we skip if not structured. | |
| // Using mapbox_directions is an option if we can parse waypoints. | |
| // As a fallback/placeholder that fulfills the requirement: | |
| console.log('Attempting calculate_distance via Mapbox Directions...'); | |
| // We'd need to extract origin/dest. | |
| // Let's try to just pass the text as query if tool supports it? | |
| // mapbox_directions takes 'waypoints'. | |
| // If we can't parse, we might skip. | |
| // But prompt says "pass origin/destination coords...". The orchestrator doesn't have them yet unless from geocode. | |
| // I'll leave a log and dummy result or try a broader search if applicable. | |
| // ACTUALLY, I'll use mapbox_search_box_text_search as a proxy if I can't do directions, OR just skip. | |
| // But the prompt wants it implemented. | |
| // I will mock it for now or assume we extracted them. | |
| // Since I can't easily parse "A to B" reliably without another LLM call or regex, I will skip with a log, | |
| // BUT strictly I should use executeAction. | |
| // I'll try to find if there is a 'natural_language_directions' tool? No. | |
| // I will construct a Mapbox Directions URL as a result to satisfy "calculate_distance" somewhat? | |
| // No, that's generate_map_link. | |
| // I'll try to geocode the whole text and see if it gives route? Unlikely. | |
| break; | |
| case 'calculate_distance': | |
| console.warn('calculate_distance operation not yet implemented - skipping'); | |
| result = { | |
| error: 'calculate_distance not implemented', | |
| description: 'Route/distance calculation requires waypoint parsing - not yet supported' | |
| }; | |
| break; |
| case 'generate_map_link': | ||
| // Generate a static map link or similar | ||
| // We can return a result object with the link | ||
| const link = `https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/${encodeURIComponent(text)}/600x400?access_token=${process.env.MAPBOX_ACCESS_TOKEN}`; | ||
| result = { | ||
| url: link, | ||
| description: "Static map link generated" | ||
| }; | ||
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap switch case declaration in a block.
The link constant declaration in the generate_map_link case can leak to other switch clauses. Static analysis correctly flags this issue.
Proposed fix
case 'generate_map_link':
- // Generate a static map link or similar
- // We can return a result object with the link
- const link = `https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/${encodeURIComponent(text)}/600x400?access_token=${process.env.MAPBOX_ACCESS_TOKEN}`;
- result = {
- url: link,
- description: "Static map link generated"
- };
- break;
+ case 'generate_map_link': {
+ const link = `https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/${encodeURIComponent(text)}/600x400?access_token=${process.env.MAPBOX_ACCESS_TOKEN}`;
+ result = {
+ url: link,
+ description: 'Static map link generated',
+ };
+ break;
+ }🧰 Tools
🪛 Biome (2.1.2)
[error] 384-386: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🤖 Prompt for AI Agents
In @lib/agents/map-control-orchestrator.tsx around lines 381 - 389, The
'generate_map_link' switch case declares a const link which can leak into other
cases; wrap the case body in its own block scope (add { ... } after case
'generate_map_link': and before break) so the const link is scoped locally and
cannot conflict with other switch clauses — update the case block that builds
the Mapbox URL and assigns result accordingly (symbols: case
'generate_map_link', const link, result).
| // Cast result to any to avoid "Property 'data' does not exist" on generic unknown | ||
| const resAny = result as any; | ||
| const data = resAny.data || resAny; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Type assertion to any obscures Composio response structure.
Casting the result to any suppresses type checking. Consider defining a proper type for Composio tool execution results or using a narrower type assertion.
🤖 Prompt for AI Agents
In @lib/agents/map-control-orchestrator.tsx around lines 423 - 425, The current
cast of result to any (resAny) hides the real Composio tool response shape;
define a proper type/interface (e.g., ComposioExecutionResult) that models the
expected fields (like data, error, etc.), replace the any assertion with a
narrow cast to that type, and extract data via const data = (result as
ComposioExecutionResult).data ?? result to preserve compatibility; update any
related helpers/types and adjust consumers of data accordingly so type-checking
enforces the Composio response structure instead of using resAny.
| if (geom.type === 'Point') { | ||
| const [lon, lat] = geom.coordinates as [number, number]; | ||
| if (Math.abs(lon) > 180 || Math.abs(lat) > 90) { | ||
| errors.push({ | ||
| field: `features[${idx}].geometry.coordinates`, | ||
| message: `Invalid coordinates: [${lon}, ${lat}]. Longitude must be -180 to 180, latitude must be -90 to 90`, | ||
| severity: 'error', | ||
| }); | ||
| } | ||
| } else if (geom.type === 'LineString') { | ||
| const coords = geom.coordinates as [number, number][]; | ||
| if (coords.length < 2) { | ||
| errors.push({ | ||
| field: `features[${idx}].geometry.coordinates`, | ||
| message: 'LineString must have at least 2 coordinates', | ||
| severity: 'error', | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Coordinate and length checks partially duplicate Zod schema validation.
The Zod schemas already enforce coordinate ranges (-180 to 180, -90 to 90) and minimum coordinate counts (LineString ≥2, Polygon ring ≥4). These semantic checks will never trigger for data that passed Zod parsing.
Consider removing redundant checks or clarifying they're intentional defense-in-depth. The duplicate ID check and ring closure check add genuine value beyond schema validation.
| // Check for reasonable values | ||
| if (cmd.params.zoom !== undefined) { | ||
| if (cmd.params.zoom < 0 || cmd.params.zoom > 22) { | ||
| warnings.push(`Unusual zoom level: ${cmd.params.zoom}. Typical range is 0-22`); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unreachable warning: Zod schema already enforces zoom range.
The MapCommandParamsSchema defines zoom: z.number().min(0).max(22), so any zoom value outside this range would cause Zod parsing to fail before reaching this check. This warning can never be triggered.
Suggested fix: remove unreachable check or adjust message
- // Check for reasonable values
- if (cmd.params.zoom !== undefined) {
- if (cmd.params.zoom < 0 || cmd.params.zoom > 22) {
- warnings.push(`Unusual zoom level: ${cmd.params.zoom}. Typical range is 0-22`);
- }
- }
+ // Zoom range is already validated by schema (0-22)📝 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.
| // Check for reasonable values | |
| if (cmd.params.zoom !== undefined) { | |
| if (cmd.params.zoom < 0 || cmd.params.zoom > 22) { | |
| warnings.push(`Unusual zoom level: ${cmd.params.zoom}. Typical range is 0-22`); | |
| } | |
| } | |
| // Zoom range is already validated by schema (0-22) |
🤖 Prompt for AI Agents
In @lib/agents/map-workers/validator.tsx around lines 187 - 192, The zoom-range
check is unreachable because MapCommandParamsSchema enforces zoom via
z.number().min(0).max(22); remove the conditional block that inspects
cmd.params.zoom and pushes the `Unusual zoom level` warning (the if
(cmd.params.zoom !== undefined) { if (cmd.params.zoom < 0 || cmd.params.zoom >
22) { warnings.push(...) } } block) or replace it with a different runtime check
if you intend to warn about other conditions; ensure no other code expects that
warning to be produced when relying on Zod validation.
| pitch: z.number().min(0).max(85).optional(), | ||
| bearing: z.number().min(0).max(360).optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bearing range may reject valid negative values.
Mapbox accepts bearing values in the range -180 to 360 (or normalized equivalents). The current constraint z.number().min(0).max(360) will reject negative bearings like -45, which are valid in Mapbox.
Proposed fix
- bearing: z.number().min(0).max(360).optional(),
+ bearing: z.number().min(-180).max(360).optional(),📝 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.
| pitch: z.number().min(0).max(85).optional(), | |
| bearing: z.number().min(0).max(360).optional(), | |
| pitch: z.number().min(0).max(85).optional(), | |
| bearing: z.number().min(-180).max(360).optional(), |
🤖 Prompt for AI Agents
In @lib/types/map-schemas.ts around lines 98 - 99, The bearing schema currently
uses z.number().min(0).max(360).optional(), which wrongly rejects valid negative
bearings; update the bearing validator in map-schemas (the bearing field next to
pitch) to allow the full Mapbox range by changing its bounds to min(-180) and
max(360) (keeping .optional()), so negative bearings like -45 are accepted.
| // Map State Feedback Schema | ||
| export const MapStateFeedbackSchema = z.object({ | ||
| success: z.boolean(), | ||
| currentBounds: z.tuple([ | ||
| z.tuple([z.number(), z.number()]), | ||
| z.tuple([z.number(), z.number()]), | ||
| ]).optional(), | ||
| currentCenter: z.tuple([z.number(), z.number()]).optional(), | ||
| currentZoom: z.number().optional(), | ||
| currentPitch: z.number().optional(), | ||
| currentBearing: z.number().optional(), | ||
| visibleFeatures: z.array(z.string()).optional(), | ||
| error: z.string().optional(), | ||
| timestamp: z.number(), | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider adding coordinate range validation for consistency.
Unlike other schemas, MapStateFeedbackSchema omits coordinate range validation for currentBounds and currentCenter. While feedback originates from the map (which should always have valid coordinates), adding validation would provide consistency with other schemas and catch unexpected data issues.
🤖 Prompt for AI Agents
In @lib/types/map-schemas.ts around lines 127 - 141, MapStateFeedbackSchema
currently accepts raw numbers for currentBounds and currentCenter; add explicit
latitude/longitude range validation to match other schemas by replacing
z.number() inside the coordinate tuples with z.number().min(-180).max(180) for
longitude and z.number().min(-90).max(90) for latitude (or the project’s chosen
order) so both currentCenter (z.tuple([lon, lat])) and each coordinate in
currentBounds’ nested tuples are validated; update MapStateFeedbackSchema to use
those bounded z.number() validators for all coordinate entries.
|
@coderabbitai GeoJSON Parser error: Error [AI_APICallError]: Invalid arguments passed to the model. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 5849 🏁 Script executed: Length of output: 806 🏁 Script executed: Length of output: 3339 🏁 Script executed: Length of output: 2893 @ngoiyaeric Thanks for the error dump — I can reproduce the root cause from the code you shared. Root cause
Immediate mitigation you can apply on Vercel (no code change)
Robust code fixes (recommended)
TypeScript patch: // lib/utils/index.ts
import { createOpenAI } from '@ai-sdk/openai'
import { createXai } from '@ai-sdk/xai'
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'
// ...existing imports...
export async function getStructuredModel(requireVision: boolean = false) {
const openaiApiKey = process.env.OPENAI_API_KEY;
const bedrockModelId = process.env.BEDROCK_MODEL_ID || 'anthropic.claude-3-5-sonnet-20241022-v2:0';
const xaiApiKey = process.env.XAI_API_KEY;
// Prefer OpenAI for structured outputs
if (openaiApiKey) {
const openai = createOpenAI({ apiKey: openaiApiKey });
// gpt-4o supports structured outputs in AI SDK
return openai('gpt-4o');
}
// Optional: fallback to Bedrock Anthropic if you’ve validated structured outputs in your setup
// const bedrock = createAmazonBedrock({ aws: { accessKeyId, secretAccessKey, region } });
// return bedrock(bedrockModelId);
// Explicitly avoid X.AI here for structured outputs
if (xaiApiKey) {
console.warn('[getStructuredModel] X.AI detected but skipped for structured outputs. Set OPENAI_API_KEY to enable structured outputs.');
}
throw new Error('No provider configured for structured outputs. Set OPENAI_API_KEY to use OpenAI for generateObject().');
}
Patch example (geojson-parser.tsx): // lib/agents/map-workers/geojson-parser.tsx
import { generateObject } from 'ai';
- import { getModel } from '@/lib/utils';
+ import { getStructuredModel } from '@/lib/utils';
export async function geojsonParser(text: string) {
- const model = await getModel();
+ const model = await getStructuredModel(); // ensures provider supports JSON schema
const { object } = await generateObject({
model,
schema: GeoJSONParserOutputSchema,
prompt: `${GEOJSON_PARSER_PROMPT}\n\nText to analyze:\n${text}`,
maxTokens: 2048,
});
return object;
}Apply the same import/substitution in:
try {
const { object } = await generateObject({ model, schema, prompt, maxTokens: 2048 });
return object;
} catch (err) {
console.warn('[geojsonParser] structured output failed, falling back to text JSON parse:', err);
const { text } = await model.generateText({ prompt: `${GEOJSON_PARSER_PROMPT}\n\nText to analyze:\n${text}\n\nOutput ONLY valid JSON matching the schema.` });
// safe-parse with Zod
const parsed = GeoJSONParserOutputSchema.safeParse(JSON.parse(text));
if (parsed.success) return parsed.data;
return { geojson: null, confidence: 0, extractedLocations: [], warnings: ['Parser fallback failed'] };
}
// app/actions.tsx (around the orchestrator invocation)
const init = await initializeComposioMapbox().catch(() => null);
const mcpClient = init?.composio ?? undefined; // ensure undefined, not null
locationResponse = await mapControlOrchestrator(answer, {
mcpClient, // now type-safe
connectionId,
enableFeedbackLoop: false,
});Sanity checks after change
console.log('[getStructuredModel] Using OpenAI gpt-4o for structured outputs');
If you’d like, I can prepare a PR-ready patch set for the three workers and utils, and add a short README blurb explaining that structured extractions are routed to OpenAI. Let me know and I’ll stage the plan. [plan_code_changes]
✏️ Learnings added
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🤖 Fix all issues with AI agents
In @lib/agents/map-workers/feedback-analyzer.tsx:
- Around line 50-65: The feedbackAnalyzer currently calls getModel() and passes
that model to generateObject, but getModel() can return X.AI grok models that
reject JSON Schema structured-output args; change feedbackAnalyzer to obtain a
structured-output-compatible model instead (e.g., implement or call
getStructuredModel()) and pass that to generateObject so schema-based generation
won't hit the error path; you can implement getStructuredModel to prefer OpenAI
(using createOpenAI + 'gpt-4o' when OPENAI_API_KEY is present) and fall back to
getModel() otherwise, then replace the model = await getModel() line in
feedbackAnalyzer with model = await getStructuredModel().
- Around line 155-163: postProcessAnalysis is mutating its input
FeedbackAnalysis object; instead return a new FeedbackAnalysis without side
effects: inside postProcessAnalysis, when attemptNumber >= 3 create and return a
shallow copy of the original (spread the top-level FeedbackAnalysis) and also
copy-and-replace the nested recommendations (spread object.recommendations and
set action='abort' and reasoning='Maximum retry attempts reached (3)...');
otherwise return a shallow copy of the original to guarantee no mutation. Ensure
you update references to FeedbackAnalysis, postProcessAnalysis,
recommendations.action and recommendations.reasoning accordingly.
In @lib/agents/map-workers/geojson-parser.tsx:
- Around line 1-6: The file geojson-parser.tsx contains no JSX and should be
renamed to geojson-parser.ts; rename the file, update any imports that reference
"geojson-parser.tsx" to "geojson-parser.ts" (search for imports of
geojson-parser), and ensure your build/TS config (tsconfig include/paths and any
ESLint rules) accepts the .ts filename; after renaming, run typecheck and the
test/build to verify no import paths or tooling assumptions remain pointing to
the .tsx name.
- Around line 72-85: geojsonParser currently calls getModel() which can return
X.AI Grok models that reject JSON Schema structured-output args and cause
generateObject to fail; introduce a getStructuredModel() helper that prefers an
OpenAI model for structured outputs (falling back to getModel()), then update
geojsonParser to call getStructuredModel() instead of getModel() before calling
generateObject so generateObject receives a model that supports structured
outputs; reference getStructuredModel, geojsonParser, generateObject, and
getModel when making the change.
- Around line 109-118: The returned warnings lose the fallback error context
when both generateObject and the fallback fail; update the error handling in the
catch/fallback block (referencing generateObject, fallbackError, and the place
that returns { geojson, confidence, extractedLocations, warnings }) to include
both the original error and the fallbackError (e.g., combine messages and/or
stacks into the warnings array), and ensure the processLogger/console.error
still logs the fallbackError while the returned warnings contain clear context
for both failures.
In @lib/agents/map-workers/map-command-generator.tsx:
- Around line 1-7: This file contains no JSX so rename map-command-generator.tsx
to map-command-generator.ts to avoid TSX parsing overhead, update any imports
that reference this module accordingly (search for references to
map-command-generator.tsx and change to .ts), verify exported symbols like
generateObject/generateText/getModel and types
MapCommandGeneratorOutputSchema/MapCommandGeneratorOutput/GeoJSONFeatureCollection
remain unchanged and that the file has no JSX; run TypeScript build/IDE to
ensure no broken imports after the rename.
- Around line 73-88: The current mapCommandGenerator calls getModel() then uses
generateObject with MapCommandGeneratorOutputSchema, but getModel() can return
X.AI Grok which rejects JSON Schema structured-output; change
mapCommandGenerator to use a model that supports structured outputs by either
(a) implementing and calling a new getStructuredModel() helper that prefers
OpenAI gpt-4o (or other structured-output-capable models) instead of getModel(),
or (b) modifying getModel() to accept a flag (e.g., { structured: true }) and
pass that flag from mapCommandGenerator; ensure the call site referencing
generateObject still passes model, schema: MapCommandGeneratorOutputSchema,
prompt, maxTokens, and that any fallback logic to generateText is only used if
generateObject errors after using the structured-capable model.
- Around line 89-136: When the text fallback JSON is parsed, the Zod validation
result (MapCommandGeneratorOutputSchema.safeParse) is ignored on failure; add
logging inside the generateObject catch fallback: after const parsed =
MapCommandGeneratorOutputSchema.safeParse(object) check the parsed.success
branch and, in the else branch, log the parsed.error (or parsed) with context
(e.g., "Map Command Generator: Zod validation failed for text fallback") before
continuing to the GeoJSON and empty-command fallbacks; ensure logging references
the generateText response (jsonText) and still returns
postProcessOutput(parsed.data) when parsed.success is true.
- Around line 160-180: postProcessOutput currently mutates its input object
(object.commands and object.estimatedDuration); make it pure by returning a new
MapCommandGeneratorOutput instead of modifying the input: in postProcessOutput
create a shallow copy of the input (e.g., newObject = { ...object }) and
copy/replace the commands array with a sorted copy (e.g., newCommands =
[...object.commands].sort(...)) and compute estimatedDuration on the newObject
without altering the original; ensure you return newObject and leave the
original object.commands and estimatedDuration unchanged.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
lib/agents/map-workers/feedback-analyzer.tsxlib/agents/map-workers/geojson-parser.tsxlib/agents/map-workers/map-command-generator.tsx
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-13T13:26:30.076Z
Learning: Workers that rely on ai.generateObject (geojsonParser, map-command-generator, feedback-analyzer) must not use the X.AI 'grok-4-fast-non-reasoning' model because X.AI’s chat/completions rejects JSON Schema structured-output arguments; prefer OpenAI gpt-4o for structured outputs.
📚 Learning: 2026-01-13T13:26:30.076Z
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-13T13:26:30.076Z
Learning: Workers that rely on ai.generateObject (geojsonParser, map-command-generator, feedback-analyzer) must not use the X.AI 'grok-4-fast-non-reasoning' model because X.AI’s chat/completions rejects JSON Schema structured-output arguments; prefer OpenAI gpt-4o for structured outputs.
Applied to files:
lib/agents/map-workers/geojson-parser.tsxlib/agents/map-workers/map-command-generator.tsx
🧬 Code graph analysis (3)
lib/agents/map-workers/geojson-parser.tsx (2)
lib/types/map-schemas.ts (2)
GeoJSONParserOutput(227-227)GeoJSONParserOutputSchema(155-160)lib/utils/index.ts (1)
getModel(20-121)
lib/agents/map-workers/feedback-analyzer.tsx (2)
lib/types/map-schemas.ts (4)
MapStateFeedback(225-225)MapCommand(223-223)FeedbackAnalysis(226-226)FeedbackAnalysisSchema(144-152)lib/utils/index.ts (1)
getModel(20-121)
lib/agents/map-workers/map-command-generator.tsx (2)
lib/types/map-schemas.ts (3)
GeoJSONFeatureCollection(222-222)MapCommandGeneratorOutput(228-228)MapCommandGeneratorOutputSchema(162-166)lib/utils/index.ts (1)
getModel(20-121)
🔇 Additional comments (9)
lib/agents/map-workers/feedback-analyzer.tsx (5)
1-33: LGTM on imports and prompt definition.The prompt is well-structured with clear role definition, analysis criteria, action definitions, and expected output format.
35-44: LGTM on interface definition.The
FeedbackAnalyzerInputinterface is well-typed and aligns with the schema types frommap-schemas.ts.
66-112: Well-implemented fallback chain.The three-tier fallback (generateObject → generateText with JSON parsing → deterministic analysis) provides robust error handling. The markdown code block stripping on line 76 and Zod validation ensures safe parsing of LLM text output.
115-153: LGTM on context builder.The
buildContextStringfunction produces a well-structured context with all relevant feedback data for the LLM to analyze.
165-253: LGTM on helper functions and synchronous fallback.The
coordinatesMatchandzoomMatcheshelpers use sensible default tolerances (0.0001 for coordinates, 0.5 for zoom). ThesimpleFeedbackAnalysisfunction provides a reliable deterministic fallback with clear status and action determination logic.lib/agents/map-workers/geojson-parser.tsx (1)
8-66: LGTM!The prompt is well-structured with clear rules, coordinate validation requirements, and good examples covering all confidence levels.
lib/agents/map-workers/map-command-generator.tsx (3)
9-57: Well-structured prompt with comprehensive command documentation.The prompt provides clear guidance on command selection, zoom levels, and camera parameters. The available commands (flyTo, easeTo, fitBounds, setCenter, setZoom) are well-documented with use cases.
Note: Per PR objectives, consider adding standalone
setPitchandsetBearingcommands if the Mapbox component supports them independently, though pitch/bearing are already covered as parameters within existing commands.
59-67: Interface is well-defined for map command generation context.The input structure appropriately captures text intent, geographic data, and optional current map state for context-aware command generation.
139-158: Context builder is functional and provides sufficient information for command generation.The function appropriately includes text, feature metadata, and current map state. One consideration: including feature coordinates in the context string could help the model make better bounding box decisions for
fitBounds, but the current approach is reasonable since the model receives the full GeoJSON schema context.
| export async function feedbackAnalyzer( | ||
| input: FeedbackAnalyzerInput | ||
| ): Promise<FeedbackAnalysis> { | ||
| const model = await getModel(); | ||
|
|
||
| const prompt = `${FEEDBACK_ANALYZER_PROMPT}\n\n${buildContextString(input)}\n\nAnalyze this feedback and provide recommendations.`; | ||
|
|
||
| try { | ||
| const { object } = await generateObject({ | ||
| model, | ||
| schema: FeedbackAnalysisSchema, | ||
| prompt, | ||
| maxTokens: 1024, | ||
| }); | ||
|
|
||
| return postProcessAnalysis(object, input.attemptNumber); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a structured-output-compatible model for generateObject.
Based on learnings, getModel() can return X.AI grok-4-fast-non-reasoning which rejects JSON Schema structured-output arguments, causing generateObject to fail. While the fallback chain handles this gracefully, it's inefficient to always trigger the error path when XAI_API_KEY is set.
Consider implementing or using a getStructuredModel() helper that prefers OpenAI gpt-4o for structured outputs, as mentioned in the PR objectives.
🔧 Suggested approach
- const model = await getModel();
+ const model = await getStructuredModel(); // Prefers OpenAI for structured outputsOr inline the OpenAI preference:
import { createOpenAI } from '@ai-sdk/openai';
async function getStructuredModel() {
const openaiApiKey = process.env.OPENAI_API_KEY;
if (openaiApiKey) {
const openai = createOpenAI({ apiKey: openaiApiKey });
return openai('gpt-4o');
}
// Fall back to default model for text generation fallback
return getModel();
}🤖 Prompt for AI Agents
In @lib/agents/map-workers/feedback-analyzer.tsx around lines 50 - 65, The
feedbackAnalyzer currently calls getModel() and passes that model to
generateObject, but getModel() can return X.AI grok models that reject JSON
Schema structured-output args; change feedbackAnalyzer to obtain a
structured-output-compatible model instead (e.g., implement or call
getStructuredModel()) and pass that to generateObject so schema-based generation
won't hit the error path; you can implement getStructuredModel to prefer OpenAI
(using createOpenAI + 'gpt-4o' when OPENAI_API_KEY is present) and fall back to
getModel() otherwise, then replace the model = await getModel() line in
feedbackAnalyzer with model = await getStructuredModel().
| function postProcessAnalysis(object: FeedbackAnalysis, attemptNumber: number): FeedbackAnalysis { | ||
| // Apply attempt-based logic | ||
| if (attemptNumber >= 3 && object.recommendations.action !== 'continue') { | ||
| // After 3 attempts, force abort if not successful | ||
| object.recommendations.action = 'abort'; | ||
| object.recommendations.reasoning = 'Maximum retry attempts reached (3). Aborting to prevent infinite loop.'; | ||
| } | ||
| return object; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Avoid mutating the input parameter.
postProcessAnalysis directly mutates the object parameter (lines 159-160). This can cause unexpected side effects if the caller retains a reference to the original object.
♻️ Suggested fix using spread operator
function postProcessAnalysis(object: FeedbackAnalysis, attemptNumber: number): FeedbackAnalysis {
// Apply attempt-based logic
if (attemptNumber >= 3 && object.recommendations.action !== 'continue') {
// After 3 attempts, force abort if not successful
- object.recommendations.action = 'abort';
- object.recommendations.reasoning = 'Maximum retry attempts reached (3). Aborting to prevent infinite loop.';
+ return {
+ ...object,
+ recommendations: {
+ ...object.recommendations,
+ action: 'abort',
+ reasoning: 'Maximum retry attempts reached (3). Aborting to prevent infinite loop.',
+ },
+ };
}
return object;
}📝 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.
| function postProcessAnalysis(object: FeedbackAnalysis, attemptNumber: number): FeedbackAnalysis { | |
| // Apply attempt-based logic | |
| if (attemptNumber >= 3 && object.recommendations.action !== 'continue') { | |
| // After 3 attempts, force abort if not successful | |
| object.recommendations.action = 'abort'; | |
| object.recommendations.reasoning = 'Maximum retry attempts reached (3). Aborting to prevent infinite loop.'; | |
| } | |
| return object; | |
| } | |
| function postProcessAnalysis(object: FeedbackAnalysis, attemptNumber: number): FeedbackAnalysis { | |
| // Apply attempt-based logic | |
| if (attemptNumber >= 3 && object.recommendations.action !== 'continue') { | |
| // After 3 attempts, force abort if not successful | |
| return { | |
| ...object, | |
| recommendations: { | |
| ...object.recommendations, | |
| action: 'abort', | |
| reasoning: 'Maximum retry attempts reached (3). Aborting to prevent infinite loop.', | |
| }, | |
| }; | |
| } | |
| return object; | |
| } |
🤖 Prompt for AI Agents
In @lib/agents/map-workers/feedback-analyzer.tsx around lines 155 - 163,
postProcessAnalysis is mutating its input FeedbackAnalysis object; instead
return a new FeedbackAnalysis without side effects: inside postProcessAnalysis,
when attemptNumber >= 3 create and return a shallow copy of the original (spread
the top-level FeedbackAnalysis) and also copy-and-replace the nested
recommendations (spread object.recommendations and set action='abort' and
reasoning='Maximum retry attempts reached (3)...'); otherwise return a shallow
copy of the original to guarantee no mutation. Ensure you update references to
FeedbackAnalysis, postProcessAnalysis, recommendations.action and
recommendations.reasoning accordingly.
| import { generateObject, generateText } from 'ai'; | ||
| import { getModel } from '@/lib/utils'; | ||
| import { | ||
| GeoJSONParserOutputSchema, | ||
| GeoJSONParserOutput, | ||
| } from '@/lib/types/map-schemas'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider renaming file to .ts extension.
This file contains no JSX; using .tsx is unnecessary and may confuse tooling or other developers.
🤖 Prompt for AI Agents
In @lib/agents/map-workers/geojson-parser.tsx around lines 1 - 6, The file
geojson-parser.tsx contains no JSX and should be renamed to geojson-parser.ts;
rename the file, update any imports that reference "geojson-parser.tsx" to
"geojson-parser.ts" (search for imports of geojson-parser), and ensure your
build/TS config (tsconfig include/paths and any ESLint rules) accepts the .ts
filename; after renaming, run typecheck and the test/build to verify no import
paths or tooling assumptions remain pointing to the .tsx name.
| export async function geojsonParser( | ||
| text: string | ||
| ): Promise<GeoJSONParserOutput> { | ||
| const model = await getModel(); | ||
|
|
||
| try { | ||
| const { object } = await generateObject({ | ||
| model, | ||
| schema: GeoJSONParserOutputSchema, | ||
| prompt: `${GEOJSON_PARSER_PROMPT}\n\nText to analyze:\n${text}`, | ||
| maxTokens: 2048, | ||
| }); | ||
|
|
||
| return validateAndReturn(object); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: getModel() may return X.AI Grok which does not support structured outputs.
Based on learnings, X.AI's grok-4-fast-non-reasoning model rejects JSON Schema structured-output arguments with a 400 error. The getModel() call can return this model, causing generateObject to always fail and fall back to generateText, adding latency and log noise.
Introduce a getStructuredModel() helper that prefers OpenAI for structured outputs, or pass a model override parameter.
🔧 Suggested fix
-import { getModel } from '@/lib/utils';
+import { getModel, getStructuredModel } from '@/lib/utils'; export async function geojsonParser(
text: string
): Promise<GeoJSONParserOutput> {
- const model = await getModel();
+ // Use structured-output-compatible model (OpenAI preferred)
+ const structuredModel = await getStructuredModel();
+ const fallbackModel = await getModel();
try {
const { object } = await generateObject({
- model,
+ model: structuredModel,
schema: GeoJSONParserOutputSchema,Then in lib/utils/index.ts, add:
export async function getStructuredModel() {
// Prefer OpenAI for structured outputs as X.AI Grok rejects JSON Schema
if (process.env.OPENAI_API_KEY) {
const { openai } = await import('@ai-sdk/openai');
return openai('gpt-4o');
}
// Fallback to default model (may not support structured outputs)
return getModel();
}🤖 Prompt for AI Agents
In @lib/agents/map-workers/geojson-parser.tsx around lines 72 - 85,
geojsonParser currently calls getModel() which can return X.AI Grok models that
reject JSON Schema structured-output args and cause generateObject to fail;
introduce a getStructuredModel() helper that prefers an OpenAI model for
structured outputs (falling back to getModel()), then update geojsonParser to
call getStructuredModel() instead of getModel() before calling generateObject so
generateObject receives a model that supports structured outputs; reference
getStructuredModel, geojsonParser, generateObject, and getModel when making the
change.
| } catch (fallbackError) { | ||
| console.error('GeoJSON Parser: Fallback failed:', fallbackError); | ||
| } | ||
|
|
||
| return { | ||
| geojson: null, | ||
| confidence: 0, | ||
| extractedLocations: [], | ||
| warnings: [`Parser error: ${error instanceof Error ? error.message : 'Unknown error'}`], | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error message loses context when fallback also fails.
When both generateObject and the fallback fail, the returned warning only includes the original error. If the fallback fails for a different reason (e.g., JSON parse error), that context is lost.
💡 Suggested fix
} catch (fallbackError) {
console.error('GeoJSON Parser: Fallback failed:', fallbackError);
+ return {
+ geojson: null,
+ confidence: 0,
+ extractedLocations: [],
+ warnings: [
+ `Primary error: ${error instanceof Error ? error.message : 'Unknown'}`,
+ `Fallback error: ${fallbackError instanceof Error ? fallbackError.message : 'Unknown'}`,
+ ],
+ };
}
return {
geojson: null,
confidence: 0,
extractedLocations: [],
- warnings: [`Parser error: ${error instanceof Error ? error.message : 'Unknown error'}`],
+ warnings: [`Parser error: ${error instanceof Error ? error.message : 'Unknown error'} (fallback also failed)`],
};🤖 Prompt for AI Agents
In @lib/agents/map-workers/geojson-parser.tsx around lines 109 - 118, The
returned warnings lose the fallback error context when both generateObject and
the fallback fail; update the error handling in the catch/fallback block
(referencing generateObject, fallbackError, and the place that returns {
geojson, confidence, extractedLocations, warnings }) to include both the
original error and the fallbackError (e.g., combine messages and/or stacks into
the warnings array), and ensure the processLogger/console.error still logs the
fallbackError while the returned warnings contain clear context for both
failures.
| function validateAndReturn(object: GeoJSONParserOutput): GeoJSONParserOutput { | ||
| if (object.geojson) { | ||
| // Validate coordinate ranges | ||
| for (const feature of object.geojson.features) { | ||
| const coords = feature.geometry.coordinates; | ||
| if (feature.geometry.type === 'Point') { | ||
| const [lon, lat] = coords as [number, number]; | ||
| if (lon < -180 || lon > 180 || lat < -90 || lat > 90) { | ||
| object.warnings = object.warnings || []; | ||
| object.warnings.push(`Invalid coordinates for feature: [${lon}, ${lat}]`); | ||
| object.confidence = Math.min(object.confidence, 0.5); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return object; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete coordinate validation: LineString and Polygon geometries are not validated.
The prompt specifies that LineString and Polygon geometries are supported, but validateAndReturn only validates Point coordinates. Invalid coordinates in routes or areas will pass through undetected.
Additionally, this function mutates the input object directly. Consider either cloning the object or documenting this side effect.
🔧 Suggested fix with full geometry validation
function validateAndReturn(object: GeoJSONParserOutput): GeoJSONParserOutput {
+ const result = { ...object, warnings: [...(object.warnings || [])] };
+
- if (object.geojson) {
- // Validate coordinate ranges
- for (const feature of object.geojson.features) {
+ if (result.geojson) {
+ for (const feature of result.geojson.features) {
const coords = feature.geometry.coordinates;
+ const invalidCoords: string[] = [];
+
+ const validatePoint = (lon: number, lat: number): boolean => {
+ if (lon < -180 || lon > 180 || lat < -90 || lat > 90) {
+ invalidCoords.push(`[${lon}, ${lat}]`);
+ return false;
+ }
+ return true;
+ };
+
if (feature.geometry.type === 'Point') {
const [lon, lat] = coords as [number, number];
- if (lon < -180 || lon > 180 || lat < -90 || lat > 90) {
- object.warnings = object.warnings || [];
- object.warnings.push(`Invalid coordinates for feature: [${lon}, ${lat}]`);
- object.confidence = Math.min(object.confidence, 0.5);
- }
+ validatePoint(lon, lat);
+ } else if (feature.geometry.type === 'LineString') {
+ for (const point of coords as [number, number][]) {
+ validatePoint(point[0], point[1]);
+ }
+ } else if (feature.geometry.type === 'Polygon') {
+ for (const ring of coords as [number, number][][]) {
+ for (const point of ring) {
+ validatePoint(point[0], point[1]);
+ }
+ }
+ }
+
+ if (invalidCoords.length > 0) {
+ result.warnings.push(`Invalid coordinates in ${feature.geometry.type}: ${invalidCoords.join(', ')}`);
+ result.confidence = Math.min(result.confidence, 0.5);
}
}
}
- return object;
+ return result;
}| import { generateObject, generateText } from 'ai'; | ||
| import { getModel } from '@/lib/utils'; | ||
| import { | ||
| MapCommandGeneratorOutputSchema, | ||
| MapCommandGeneratorOutput, | ||
| GeoJSONFeatureCollection, | ||
| } from '@/lib/types/map-schemas'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider using .ts extension instead of .tsx.
This file contains no JSX elements, so a .ts extension would be more accurate and avoids unnecessary TSX parsing overhead.
🤖 Prompt for AI Agents
In @lib/agents/map-workers/map-command-generator.tsx around lines 1 - 7, This
file contains no JSX so rename map-command-generator.tsx to
map-command-generator.ts to avoid TSX parsing overhead, update any imports that
reference this module accordingly (search for references to
map-command-generator.tsx and change to .ts), verify exported symbols like
generateObject/generateText/getModel and types
MapCommandGeneratorOutputSchema/MapCommandGeneratorOutput/GeoJSONFeatureCollection
remain unchanged and that the file has no JSX; run TypeScript build/IDE to
ensure no broken imports after the rename.
| export async function mapCommandGenerator( | ||
| input: MapCommandGeneratorInput | ||
| ): Promise<MapCommandGeneratorOutput> { | ||
| const model = await getModel(); | ||
|
|
||
| const prompt = `${MAP_COMMAND_GENERATOR_PROMPT}\n\n${buildContextString(input)}\n\nGenerate appropriate map commands for this context.`; | ||
|
|
||
| try { | ||
| const { object } = await generateObject({ | ||
| model, | ||
| schema: MapCommandGeneratorOutputSchema, | ||
| prompt, | ||
| maxTokens: 1536, | ||
| }); | ||
|
|
||
| return postProcessOutput(object); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: getModel() may return X.AI Grok which doesn't support structured outputs.
Based on learnings, workers using generateObject must not use the X.AI 'grok-4-fast-non-reasoning' model because X.AI's chat/completions endpoint rejects JSON Schema structured-output arguments with a 400 error. The current getModel() defaults to Grok first (per the util snippet), causing generateObject to fail and forcing unnecessary fallback to generateText.
Consider implementing a getStructuredModel() helper that prefers OpenAI gpt-4o for structured outputs, or pass a flag to getModel() to indicate structured output requirement.
Suggested approach
- const model = await getModel();
+ // Use a model that supports structured outputs (OpenAI preferred)
+ const model = await getStructuredModel(); // or getModel({ requireStructuredOutput: true })The getStructuredModel() helper should prioritize OpenAI gpt-4o which reliably supports generateObject with JSON Schema.
🤖 Prompt for AI Agents
In @lib/agents/map-workers/map-command-generator.tsx around lines 73 - 88, The
current mapCommandGenerator calls getModel() then uses generateObject with
MapCommandGeneratorOutputSchema, but getModel() can return X.AI Grok which
rejects JSON Schema structured-output; change mapCommandGenerator to use a model
that supports structured outputs by either (a) implementing and calling a new
getStructuredModel() helper that prefers OpenAI gpt-4o (or other
structured-output-capable models) instead of getModel(), or (b) modifying
getModel() to accept a flag (e.g., { structured: true }) and pass that flag from
mapCommandGenerator; ensure the call site referencing generateObject still
passes model, schema: MapCommandGeneratorOutputSchema, prompt, maxTokens, and
that any fallback logic to generateText is only used if generateObject errors
after using the structured-capable model.
| } catch (error) { | ||
| console.warn('Map Command Generator: generateObject failed, attempting fallback to generateText:', error); | ||
|
|
||
| try { | ||
| const { text: jsonText } = await generateText({ | ||
| model, | ||
| prompt: `${prompt}\n\nIMPORTANT: Return ONLY valid JSON matching the Output Structure. Do not include markdown formatting.`, | ||
| maxTokens: 1536, | ||
| }); | ||
|
|
||
| const cleanedText = jsonText.replace(/```json\n?|\n?```/g, '').trim(); | ||
| const object = JSON.parse(cleanedText); | ||
|
|
||
| const parsed = MapCommandGeneratorOutputSchema.safeParse(object); | ||
| if (parsed.success) { | ||
| return postProcessOutput(parsed.data); | ||
| } | ||
| } catch (fallbackError) { | ||
| console.error('Map Command Generator: Fallback failed:', fallbackError); | ||
| } | ||
|
|
||
| // Fallback: Generate basic command based on GeoJSON | ||
| if (input.geojson && input.geojson.features.length > 0) { | ||
| const firstFeature = input.geojson.features[0]; | ||
| if (firstFeature.geometry.type === 'Point') { | ||
| const [lon, lat] = firstFeature.geometry.coordinates as [number, number]; | ||
| return { | ||
| commands: [{ | ||
| command: 'flyTo', | ||
| params: { | ||
| center: [lon, lat], | ||
| zoom: 12, | ||
| essential: true, | ||
| }, | ||
| priority: 1, | ||
| }], | ||
| reasoning: 'Fallback: Flying to first point feature', | ||
| estimatedDuration: 2000, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| commands: [], | ||
| reasoning: `Command generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, | ||
| estimatedDuration: 0, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Robust fallback chain with good error recovery.
The three-tier fallback (generateText → GeoJSON Point → empty commands) provides graceful degradation. The Zod safeParse validation on line 102 ensures schema compliance before using text-generated output.
Minor improvement: Consider logging when Zod validation fails (line 102-105) to aid debugging, as currently it falls through silently to the GeoJSON fallback.
Optional: Add logging for Zod validation failure
const parsed = MapCommandGeneratorOutputSchema.safeParse(object);
if (parsed.success) {
return postProcessOutput(parsed.data);
+ } else {
+ console.warn('Map Command Generator: Zod validation failed:', parsed.error.issues);
}📝 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.
| } catch (error) { | |
| console.warn('Map Command Generator: generateObject failed, attempting fallback to generateText:', error); | |
| try { | |
| const { text: jsonText } = await generateText({ | |
| model, | |
| prompt: `${prompt}\n\nIMPORTANT: Return ONLY valid JSON matching the Output Structure. Do not include markdown formatting.`, | |
| maxTokens: 1536, | |
| }); | |
| const cleanedText = jsonText.replace(/```json\n?|\n?```/g, '').trim(); | |
| const object = JSON.parse(cleanedText); | |
| const parsed = MapCommandGeneratorOutputSchema.safeParse(object); | |
| if (parsed.success) { | |
| return postProcessOutput(parsed.data); | |
| } | |
| } catch (fallbackError) { | |
| console.error('Map Command Generator: Fallback failed:', fallbackError); | |
| } | |
| // Fallback: Generate basic command based on GeoJSON | |
| if (input.geojson && input.geojson.features.length > 0) { | |
| const firstFeature = input.geojson.features[0]; | |
| if (firstFeature.geometry.type === 'Point') { | |
| const [lon, lat] = firstFeature.geometry.coordinates as [number, number]; | |
| return { | |
| commands: [{ | |
| command: 'flyTo', | |
| params: { | |
| center: [lon, lat], | |
| zoom: 12, | |
| essential: true, | |
| }, | |
| priority: 1, | |
| }], | |
| reasoning: 'Fallback: Flying to first point feature', | |
| estimatedDuration: 2000, | |
| }; | |
| } | |
| } | |
| return { | |
| commands: [], | |
| reasoning: `Command generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, | |
| estimatedDuration: 0, | |
| }; | |
| } | |
| } catch (error) { | |
| console.warn('Map Command Generator: generateObject failed, attempting fallback to generateText:', error); | |
| try { | |
| const { text: jsonText } = await generateText({ | |
| model, | |
| prompt: `${prompt}\n\nIMPORTANT: Return ONLY valid JSON matching the Output Structure. Do not include markdown formatting.`, | |
| maxTokens: 1536, | |
| }); | |
| const cleanedText = jsonText.replace(/ |
🤖 Prompt for AI Agents
In @lib/agents/map-workers/map-command-generator.tsx around lines 89 - 136, When
the text fallback JSON is parsed, the Zod validation result
(MapCommandGeneratorOutputSchema.safeParse) is ignored on failure; add logging
inside the generateObject catch fallback: after const parsed =
MapCommandGeneratorOutputSchema.safeParse(object) check the parsed.success
branch and, in the else branch, log the parsed.error (or parsed) with context
(e.g., "Map Command Generator: Zod validation failed for text fallback") before
continuing to the GeoJSON and empty-command fallbacks; ensure logging references
the generateText response (jsonText) and still returns
postProcessOutput(parsed.data) when parsed.success is true.
| function postProcessOutput(object: MapCommandGeneratorOutput): MapCommandGeneratorOutput { | ||
| // Sort commands by priority if specified | ||
| if (object.commands.length > 1) { | ||
| object.commands.sort((a, b) => (a.priority || 0) - (b.priority || 0)); | ||
| } | ||
|
|
||
| // Calculate estimated duration if not provided | ||
| if (!object.estimatedDuration && object.commands.length > 0) { | ||
| object.estimatedDuration = object.commands.reduce((total, cmd) => { | ||
| if (cmd.command === 'flyTo') { | ||
| return total + (cmd.params.duration || 2000); | ||
| } else if (cmd.command === 'easeTo') { | ||
| return total + (cmd.params.duration || 1000); | ||
| } else if (cmd.command === 'fitBounds') { | ||
| return total + 1500; | ||
| } | ||
| return total + 500; | ||
| }, 0); | ||
| } | ||
| return object; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Post-processing logic correctly handles priority sorting and duration estimation.
The sorting and duration calculation are implemented correctly. Note that this function mutates the input object directly (lines 163, 168). This is functionally fine since the object is returned immediately, but for clarity and testability, consider returning a new object instead.
Optional: Avoid mutation for purity
function postProcessOutput(object: MapCommandGeneratorOutput): MapCommandGeneratorOutput {
+ const commands = [...object.commands];
// Sort commands by priority if specified
- if (object.commands.length > 1) {
- object.commands.sort((a, b) => (a.priority || 0) - (b.priority || 0));
+ if (commands.length > 1) {
+ commands.sort((a, b) => (a.priority || 0) - (b.priority || 0));
}
+ let estimatedDuration = object.estimatedDuration;
// Calculate estimated duration if not provided
- if (!object.estimatedDuration && object.commands.length > 0) {
- object.estimatedDuration = object.commands.reduce((total, cmd) => {
+ if (!estimatedDuration && commands.length > 0) {
+ estimatedDuration = commands.reduce((total, cmd) => {
// ... same logic
}, 0);
}
- return object;
+ return { ...object, commands, estimatedDuration };
}🤖 Prompt for AI Agents
In @lib/agents/map-workers/map-command-generator.tsx around lines 160 - 180,
postProcessOutput currently mutates its input object (object.commands and
object.estimatedDuration); make it pure by returning a new
MapCommandGeneratorOutput instead of modifying the input: in postProcessOutput
create a shallow copy of the input (e.g., newObject = { ...object }) and
copy/replace the commands array with a sorted copy (e.g., newCommands =
[...object.commands].sort(...)) and compute estimatedDuration on the newObject
without altering the original; ensure you return newObject and leave the
original object.commands and estimatedDuration unchanged.
|
@coderabbitai the prompts that should render the map are just freezing with no output Error fetching chat PCAOi8oArgEiFSd4WELVN from DB: TypeError: Cannot read properties of undefined (reading 'searchParams') |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 10928 🏁 Script executed: Length of output: 6027 @ngoiyaeric, I found the exact root cause of the freeze! 🎯 🚨 The ProblemYour map prompts freeze because:
✅ The FixI need to implement [plan_code_changes] Implementation: Fix Map Freeze by Adding Structured Output SupportCurrent broken flow:Fixed flow:🎯 What This FixesBefore (Current - Freezes):After (Fixed - Works):🚀 Quick Mitigation (Before Code Changes)If you need an immediate fix in Vercel, set these environment variables: OPENAI_API_KEY=your_openai_key_hereThis will make 📊 Expected Results After FixThese queries will work without freezing:
All with proper geocoding via Composio and no freezes! 🎉 Would you like me to proceed with generating these code changes? 🧠 Learnings used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @app/actions.tsx:
- Around line 677-678: The const declaration for relatedQueries created via
createStreamableValue<PartialRelated>() is inside a switch case without braces,
which can leak scope; wrap the entire switch case branch that uses
relatedQueries in a block (add { ... } around the case body) so the const is
lexically scoped to that case, and ensure relatedQueries.done(JSON.parse(content
as string)) remains inside that new block.
- Around line 408-448: The mapControlOrchestrator currently calls generateObject
using getModel(), which can return X.AI's grok model that rejects JSON Schema
structured-output and will break at runtime; modify mapControlOrchestrator so
the specific generateObject call uses OpenAI's gpt-4o model (not getModel()) by
either creating an OpenAI client/instantiating an OpenAI model instance locally
for that call or by passing an explicit model: 'gpt-4o' option into
generateObject; keep all other orchestrator logic the same (geojsonEnricherV2
remains unchanged).
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
app/actions.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-13T13:26:30.076Z
Learning: Workers that rely on ai.generateObject (geojsonParser, map-command-generator, feedback-analyzer) must not use the X.AI 'grok-4-fast-non-reasoning' model because X.AI’s chat/completions rejects JSON Schema structured-output arguments; prefer OpenAI gpt-4o for structured outputs.
🪛 Biome (2.1.2)
app/actions.tsx
[error] 677-678: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (5)
app/actions.tsx (5)
16-34: LGTM!The new imports are well-organized and correctly bring in the orchestrator, MCP initialization, type definitions, and UI handlers needed for the geospatial enrichment flow.
73-73: LGTM!JSON serialization of multimodal content ensures consistent storage format for messages containing both text and image parts.
322-322: LGTM!The type cast to
PartialInquiryaligns with the partial schema pattern, and the defensive optional chaining oninquiry?.questionhandles potential undefined values.
466-475: LGTM!The geojsonEnrichment tool output is correctly stored in AI state with proper serialization, enabling UI reconstruction via
getUIStateFromAIState.
730-749: Defensive validation for geojsonEnrichment tool output is adequate.The validation correctly checks the critical fields:
text(string) andgeojson(null or FeatureCollection). Whilemap_commandsis part of theLocationResponsetype, it is optional andLocationResponseHandleralready handles missing/undefined values gracefully through optional chaining (map_commands?.length,map_commands?.map()). Additional validation formap_commandsis not necessary.
| let locationResponse; | ||
| let mcpClient: Composio | undefined = undefined; | ||
| let connectionId = ''; | ||
|
|
||
| try { | ||
| // Only attempt to initialize if we have reason to believe it might work | ||
| // or if we want to log the failure. | ||
| if (process.env.COMPOSIO_API_KEY && process.env.MAPBOX_ACCESS_TOKEN && process.env.COMPOSIO_USER_ID) { | ||
| const composioData = await initializeComposioMapbox(); | ||
| mcpClient = composioData.composio; | ||
| connectionId = composioData.connectionId; | ||
| console.log('🔌 Composio client initialized for Orchestrator'); | ||
| } else { | ||
| console.log('ℹ️ Composio env vars missing, skipping MCP initialization'); | ||
| } | ||
| } catch (e) { | ||
| console.warn("Failed to initialize Composio for orchestrator (proceeding without MCP):", e); | ||
| } | ||
|
|
||
| try { | ||
| // Use the Orchestrator with fallback to basic enricher handled internally if needed | ||
| console.log('🚀 Starting Map Control Orchestrator...'); | ||
| locationResponse = await mapControlOrchestrator(answer, { | ||
| mcpClient, | ||
| connectionId, | ||
| enableFeedbackLoop: false // Disable for now to reduce latency | ||
| }); | ||
| console.log('✨ Orchestrator finished:', locationResponse); | ||
| } catch (e) { | ||
| console.error("Error during map orchestration:", e); | ||
| // Fallback to basic enricher | ||
| try { | ||
| locationResponse = await geojsonEnricherV2(answer); | ||
| } catch (fallbackErr) { | ||
| locationResponse = { | ||
| text: answer, | ||
| geojson: null, | ||
| map_commands: null, | ||
| }; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify model selection in orchestrator and enricher to ensure X.AI is not used for structured outputs
echo "=== Checking mapControlOrchestrator for model usage ==="
fd 'map-control-orchestrator' --type f | head -5Repository: QueueLab/QCX
Length of output: 151
🏁 Script executed:
#!/bin/bash
# Find the relevant orchestrator and enricher files
echo "=== Finding orchestrator and enricher files ==="
fd -i 'orchestrator|enricher' --type f | grep -E '\.(ts|tsx|js)$' | head -20Repository: QueueLab/QCX
Length of output: 227
🏁 Script executed:
#!/bin/bash
# Check mapControlOrchestrator for generateObject and model selection
echo "=== mapControlOrchestrator.tsx - searching for generateObject ==="
rg -n 'generateObject|model|grok' lib/agents/map-control-orchestrator.tsx -A3 -B1
echo ""
echo "=== geojsonEnricherV2 - searching for generateObject ==="
rg -n 'generateObject|model|grok' lib/agents/geojson-enricher-v2.tsx -A3 -B1Repository: QueueLab/QCX
Length of output: 1224
🏁 Script executed:
#!/bin/bash
# Find and examine getModel implementation
echo "=== getModel() implementation ==="
rg -n 'export.*getModel|function getModel' lib/utils -A10 | head -50Repository: QueueLab/QCX
Length of output: 871
🏁 Script executed:
#!/bin/bash
# Get full getModel() implementation
echo "=== Full getModel() implementation ==="
sed -n '20,100p' lib/utils/index.tsRepository: QueueLab/QCX
Length of output: 2932
Critical issue: mapControlOrchestrator uses generateObject with X.AI grok model, which will fail at runtime.
The mapControlOrchestrator calls generateObject with a model from getModel(). However, getModel() defaults to X.AI's grok-4-fast-non-reasoning when available. X.AI's API rejects JSON Schema structured-output arguments, causing generateObject calls to fail at runtime.
Fix: Modify the orchestrator to use OpenAI's gpt-4o for the generateObject call (line 302-305). Either create a separate helper for structured outputs or explicitly instantiate OpenAI within the orchestrator for this call.
geojsonEnricherV2 uses streamText instead of generateObject, so it is unaffected.
🤖 Prompt for AI Agents
In @app/actions.tsx around lines 408 - 448, The mapControlOrchestrator currently
calls generateObject using getModel(), which can return X.AI's grok model that
rejects JSON Schema structured-output and will break at runtime; modify
mapControlOrchestrator so the specific generateObject call uses OpenAI's gpt-4o
model (not getModel()) by either creating an OpenAI client/instantiating an
OpenAI model instance locally for that call or by passing an explicit model:
'gpt-4o' option into generateObject; keep all other orchestrator logic the same
(geojsonEnricherV2 remains unchanged).
| const relatedQueries = createStreamableValue<PartialRelated>() | ||
| relatedQueries.done(JSON.parse(content as string)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap switch case declaration in a block to prevent scoping issues.
The const relatedQueries declaration inside the switch case without braces can be erroneously accessed by other switch clauses, violating proper lexical scoping rules.
🔧 Proposed fix
case 'related':
- const relatedQueries = createStreamableValue<PartialRelated>()
- relatedQueries.done(JSON.parse(content as string))
- return {
- id,
- component: (
- <Section title="Related" separator={true}>
- <SearchRelated relatedQueries={relatedQueries.value} />
- </Section>
- )
- }
+ {
+ const relatedQueries = createStreamableValue<PartialRelated>()
+ relatedQueries.done(JSON.parse(content as string))
+ return {
+ id,
+ component: (
+ <Section title="Related" separator={true}>
+ <SearchRelated relatedQueries={relatedQueries.value} />
+ </Section>
+ )
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const relatedQueries = createStreamableValue<PartialRelated>() | |
| relatedQueries.done(JSON.parse(content as string)) | |
| case 'related': | |
| { | |
| const relatedQueries = createStreamableValue<PartialRelated>() | |
| relatedQueries.done(JSON.parse(content as string)) | |
| return { | |
| id, | |
| component: ( | |
| <Section title="Related" separator={true}> | |
| <SearchRelated relatedQueries={relatedQueries.value} /> | |
| </Section> | |
| ) | |
| } | |
| } |
🧰 Tools
🪛 Biome (2.1.2)
[error] 677-678: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🤖 Prompt for AI Agents
In @app/actions.tsx around lines 677 - 678, The const declaration for
relatedQueries created via createStreamableValue<PartialRelated>() is inside a
switch case without braces, which can leak scope; wrap the entire switch case
branch that uses relatedQueries in a block (add { ... } around the case body) so
the const is lexically scoped to that case, and ensure
relatedQueries.done(JSON.parse(content as string)) remains inside that new
block.
- Replaced the procedural MapControlOrchestrator with a LangGraph StateGraph implementation in lib/agents/map-langgraph.tsx. - Implemented nodes for classification, MCP tool execution (geocoding), GeoJSON parsing, and map command generation. - Added a MapStatus component to stream intermediate orchestration steps to the UI. - Integrated the new orchestrator into app/actions.tsx using runMapGraph. - Added required dependencies. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…-integration-17076216558174372852 Integrate LangGraph for Mapbox GeoJSON control
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/actions.tsx (1)
464-494:⚠️ Potential issue | 🟡 MinorTool message should use
groupeIdinstead ofnanoid()for consistency with other messages in the block.At line 475, the
geojsonEnrichmenttool message usesnanoid()while the assistantresponse(line 469),related(line 482), andfollowup(line 488) messages all usegroupeId. This breaks the established pattern elsewhere in the file (e.g., line 371) where tool messages share the same group ID. If message grouping is meaningful for UI reconstruction or chat persistence, this inconsistency could cause the tool output to be orphaned from its conversation group.
🤖 Fix all issues with AI agents
In `@app/actions.tsx`:
- Around line 427-453: statusStream created via createStreamableUI is not closed
if runMapGraph throws, leaving the UI hanging; ensure statusStream.done() is
always called by moving the cleanup into a finally (or calling
statusStream.done() at the start of the catch) so the MapStatus stream is
terminated before running the geojsonEnricherV2 fallback or returning the
fallback response; update the block around runMapGraph / statusStream
(references: statusStream, createStreamableUI, statusStream.done(), runMapGraph,
geojsonEnricherV2) to guarantee statusStream.done() runs on both success and
error paths.
In `@components/map/map-status.tsx`:
- Line 13: The span in the MapStatus component uses both "text-xs" and
"text-[10px]", which is redundant since "text-[10px]" overrides "text-xs";
remove "text-xs" from the className on the span (the element that currently
contains "text-xs font-medium text-muted-foreground uppercase tracking-wider
text-[10px]") so only the explicit "text-[10px]" (along with font-medium,
text-muted-foreground, uppercase, tracking-wider) remains.
- Line 4: The imported symbol Map from 'lucide-react' shadows the global Map
constructor; rename the import to avoid the conflict by changing the import line
to use an alias (e.g., import { Map as MapIcon } from 'lucide-react') and update
any JSX usage in this component (e.g., replace <Map .../> with <MapIcon .../>),
making sure no other references to Map remain in map-status.tsx so the built-in
Map stays unshadowed.
In `@lib/agents/index.tsx`:
- Line 6: The index currently re-exports everything from map-langgraph which
exposes the internal mapGraph StateGraph; change map-langgraph.tsx to use named
exports for runMapGraph and mapGraph (if not already) and then update
lib/agents/index.tsx to only re-export runMapGraph (not export *). Specifically,
keep the public API as runMapGraph and stop exporting mapGraph from the barrel
so callers cannot import the internal StateGraph implementation.
In `@lib/agents/map-langgraph.tsx`:
- Line 173: The call to addConditionalEdges("validator", shouldContinue as any,
...) suppresses type checking by casting shouldContinue to any; add a concise
TODO comment immediately above this call explaining that the as any cast is
required because of a known LangGraph TypeScript API ergonomic gap in the
conditional-edge signature, reference the symbols addConditionalEdges and
shouldContinue, and include a link or issue number if available and a note to
remove the cast once LangGraph fixes the types so future maintainers understand
why the cast exists.
- Line 43: The mcpClient parameter is typed as Annotation<any>, losing type
safety; replace the any with a concrete type that matches the client API used by
mcpExecutorNode—either import and use the Composio type from `@composio/core`
(e.g., Composio or the appropriate client interface) or declare a narrow
interface (e.g., containing tools.execute(method: string, args: any):
Promise<any>) and use that type for mcpClient; update the function/signature
where mcpClient is declared and any call sites in mcpExecutorNode to satisfy the
stronger type.
- Around line 150-158: The shouldContinue router can loop futilely because
re-invoking the "classifier" uses the same MapGraphState.State
(researcherResponse unchanged) and will repeat failures if mcpClient is absent
or geocoding produced no features; update shouldContinue to avoid retrying when
mcpClient is missing (check state.mcpClient or equivalent and return END) or,
alternatively, change the classifier input before retry by setting a flag or
appending feedback into state.researcherResponse (e.g., add a "retryFeedback"
note describing "no mcpClient" or "no geojson features") and only loop back when
that feedback has been added so the classifier gets new context; reference
shouldContinue, MapGraphState.State, state.researcherResponse, state.mcpClient,
state.geojson, state.classification, and state.iteration when making the change.
- Around line 48-70: The classifierNode currently calls generateObject with
model = await getModel(), which defaults to X.AI grok and will reject JSON
Schema structured-output; update classifierNode so generateObject is passed a
model that supports structured outputs (e.g., OpenAI gpt-4o) instead of the
default getModel() result. Concretely, replace the model assignment used in
generateObject (the variable referenced in the generateObject call inside
classifierNode) with a dedicated OpenAI model selection (or call a helper that
returns OpenAI gpt-4o), keeping the rest (ORCHESTRATOR_CLASSIFICATION_PROMPT,
QueryClassificationSchema, generateObject) intact so structured-output is
accepted. Ensure the unique symbols affected are classifierNode, getModel,
generateObject, QueryClassificationSchema, and
ORCHESTRATOR_CLASSIFICATION_PROMPT.
- Around line 198-211: The code assumes mapGraph.stream(initialState, {
streamMode: "values" }) yields full state snapshots and then calls
options.onStatusUpdate multiple times per update; confirm streamMode is
supported and returns complete states in your LangGraph version, then change the
sequential status checks inside the for-await loop (the block updating
finalResult and calling options.onStatusUpdate) to use an exclusive chain (if /
else if / else if / else) so only one status message fires per streamed update
(reference mapGraph.stream, finalResult, update, and options.onStatusUpdate).
In `@package.json`:
- Around line 22-33: Remove the three unused dependencies from package.json:
delete the entries for "@langchain/google-genai", "@langchain/openai", and
"@ai-sdk/langchain" (leave "@langchain/langgraph" and "@langchain/core" intact
as langgraph is used in lib/agents/map-langgraph.tsx); after editing
package.json run the package manager install (npm/yarn/pnpm) and run the
test/build to ensure nothing else imports those packages and no transitive
changes break the project.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (5)
app/actions.tsxcomponents/map/map-status.tsxlib/agents/index.tsxlib/agents/map-langgraph.tsxpackage.json
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-13T13:26:30.086Z
Learning: Workers that rely on ai.generateObject (geojsonParser, map-command-generator, feedback-analyzer) must not use the X.AI 'grok-4-fast-non-reasoning' model because X.AI’s chat/completions rejects JSON Schema structured-output arguments; prefer OpenAI gpt-4o for structured outputs.
📚 Learning: 2026-01-13T13:26:30.086Z
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-13T13:26:30.086Z
Learning: Workers that rely on ai.generateObject (geojsonParser, map-command-generator, feedback-analyzer) must not use the X.AI 'grok-4-fast-non-reasoning' model because X.AI’s chat/completions rejects JSON Schema structured-output arguments; prefer OpenAI gpt-4o for structured outputs.
Applied to files:
app/actions.tsx
📚 Learning: 2026-01-17T06:14:51.070Z
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-17T06:14:51.070Z
Learning: For structured output tasks using ai.generateObject (resolution-search, geojsonParser, map-command-generator, feedback-analyzer), prefer OpenAI gpt-4o. While xAI's grok-4-1-fast-reasoning technically supports structured outputs and vision, OpenAI has proven more reliable integration with the AI SDK's generateObject function and Zod schemas in production.
Applied to files:
app/actions.tsx
🧬 Code graph analysis (1)
components/map/map-status.tsx (1)
components/ui/spinner.tsx (1)
Spinner(6-19)
🪛 Biome (2.3.13)
components/map/map-status.tsx
[error] 4-4: Do not shadow the global "Map" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
app/actions.tsx
[error] 682-683: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (2)
app/actions.tsx (2)
15-34: Imports look correctly wired to the new orchestration flow.
runMapGraphreplaces the previously unusedmapControlOrchestrator, andinitializeComposioMapbox/Composioare now properly consumed in the Composio initialization block (lines 415–419). The addition ofgeojsonEnricherV2as a fallback enricher andLocationResponseHandlerfor UI rendering aligns with the new pipeline.
407-425: Well-structured Composio initialization with env-var guards and graceful fallback.The check for
COMPOSIO_API_KEY,MAPBOX_ACCESS_TOKEN, andCOMPOSIO_USER_IDbefore attempting initialization (line 415) prevents unnecessary failures, and the try/catch ensures the flow continues without MCP if initialization fails. Good defensive pattern.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| try { | ||
| // Use the Orchestrator with fallback to basic enricher handled internally if needed | ||
| console.log("🚀 Starting LangGraph Map Orchestrator..."); | ||
| const statusStream = createStreamableUI(<MapStatus status="Initializing..." />); | ||
| uiStream.append(statusStream.value); | ||
|
|
||
| locationResponse = await runMapGraph(answer, { | ||
| mcpClient, | ||
| connectionId, | ||
| onStatusUpdate: (status) => { | ||
| statusStream.update(<MapStatus status={status} />); | ||
| } | ||
| }); | ||
| statusStream.done(); | ||
| } catch (e) { | ||
| console.error("Error during map orchestration:", e); | ||
| // Fallback to basic enricher | ||
| try { | ||
| locationResponse = await geojsonEnricherV2(answer); | ||
| } catch (fallbackErr) { | ||
| locationResponse = { | ||
| text: answer, | ||
| geojson: null, | ||
| map_commands: null, | ||
| }; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
statusStream is not closed on the error path — this will cause the UI to hang.
If runMapGraph throws (line 441), execution jumps to the catch block, but statusStream.done() is only called inside the try (line 440). The unclosed streamable UI will leave the MapStatus component in a perpetual loading state.
Proposed fix
} catch (e) {
console.error("Error during map orchestration:", e);
+ statusStream.done();
// Fallback to basic enricher
try {
locationResponse = await geojsonEnricherV2(answer);🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 427 - 453, statusStream created via
createStreamableUI is not closed if runMapGraph throws, leaving the UI hanging;
ensure statusStream.done() is always called by moving the cleanup into a finally
(or calling statusStream.done() at the start of the catch) so the MapStatus
stream is terminated before running the geojsonEnricherV2 fallback or returning
the fallback response; update the block around runMapGraph / statusStream
(references: statusStream, createStreamableUI, statusStream.done(), runMapGraph,
geojsonEnricherV2) to guarantee statusStream.done() runs on both success and
error paths.
| 'use client' | ||
|
|
||
| import { Spinner } from '@/components/ui/spinner' | ||
| import { Map } from 'lucide-react' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename the Map import to avoid shadowing the global Map constructor.
Biome flags this as shadowing the built-in Map. While it won't cause a bug in this small component, renaming the import avoids confusion and satisfies the linter.
Proposed fix
-import { Map } from 'lucide-react'
+import { Map as MapIcon } from 'lucide-react'Then on line 10:
- <Map className="w-4 h-4" />
+ <MapIcon className="w-4 h-4" />🧰 Tools
🪛 Biome (2.3.13)
[error] 4-4: Do not shadow the global "Map" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
🤖 Prompt for AI Agents
In `@components/map/map-status.tsx` at line 4, The imported symbol Map from
'lucide-react' shadows the global Map constructor; rename the import to avoid
the conflict by changing the import line to use an alias (e.g., import { Map as
MapIcon } from 'lucide-react') and update any JSX usage in this component (e.g.,
replace <Map .../> with <MapIcon .../>), making sure no other references to Map
remain in map-status.tsx so the built-in Map stays unshadowed.
| <Map className="w-4 h-4" /> | ||
| </div> | ||
| <div className="flex flex-col gap-0.5"> | ||
| <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider text-[10px]">Map Engine</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Redundant Tailwind class: text-xs is overridden by text-[10px].
text-xs sets font-size to 12px, but text-[10px] overrides it to 10px. Remove text-xs to avoid confusion.
- <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider text-[10px]">Map Engine</span>
+ <span className="font-medium text-muted-foreground uppercase tracking-wider text-[10px]">Map Engine</span>📝 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.
| <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider text-[10px]">Map Engine</span> | |
| <span className="font-medium text-muted-foreground uppercase tracking-wider text-[10px]">Map Engine</span> |
🤖 Prompt for AI Agents
In `@components/map/map-status.tsx` at line 13, The span in the MapStatus
component uses both "text-xs" and "text-[10px]", which is redundant since
"text-[10px]" overrides "text-xs"; remove "text-xs" from the className on the
span (the element that currently contains "text-xs font-medium
text-muted-foreground uppercase tracking-wider text-[10px]") so only the
explicit "text-[10px]" (along with font-medium, text-muted-foreground,
uppercase, tracking-wider) remains.
| export * from './query-suggestor' | ||
| export * from './researcher' | ||
| export * from './resolution-search' | ||
| export * from './map-langgraph' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider whether mapGraph (the raw compiled graph) should be part of the public API.
export * from './map-langgraph' exposes both runMapGraph (the intended entry point) and mapGraph (the raw compiled StateGraph). If mapGraph is an implementation detail, use named exports in map-langgraph.tsx and only re-export runMapGraph here to keep the public surface minimal.
🤖 Prompt for AI Agents
In `@lib/agents/index.tsx` at line 6, The index currently re-exports everything
from map-langgraph which exposes the internal mapGraph StateGraph; change
map-langgraph.tsx to use named exports for runMapGraph and mapGraph (if not
already) and then update lib/agents/index.tsx to only re-export runMapGraph (not
export *). Specifically, keep the public API as runMapGraph and stop exporting
mapGraph from the barrel so callers cannot import the internal StateGraph
implementation.
| }), | ||
| feedback: Annotation<MapStateFeedback | undefined>, | ||
| error: Annotation<string | undefined>, | ||
| mcpClient: Annotation<any>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
mcpClient is typed as any, losing all type safety.
Consider using a more specific type (e.g., Composio from @composio/core) or at minimum a structural interface describing the .tools.execute() method signature used in mcpExecutorNode.
🤖 Prompt for AI Agents
In `@lib/agents/map-langgraph.tsx` at line 43, The mcpClient parameter is typed as
Annotation<any>, losing type safety; replace the any with a concrete type that
matches the client API used by mcpExecutorNode—either import and use the
Composio type from `@composio/core` (e.g., Composio or the appropriate client
interface) or declare a narrow interface (e.g., containing tools.execute(method:
string, args: any): Promise<any>) and use that type for mcpClient; update the
function/signature where mcpClient is declared and any call sites in
mcpExecutorNode to satisfy the stronger type.
| const classifierNode = async (state: typeof MapGraphState.State) => { | ||
| console.log("--- NODE: CLASSIFIER ---"); | ||
| const model = await getModel(); | ||
| const ORCHESTRATOR_CLASSIFICATION_PROMPT = `Analyze the following response from a researcher agent and determine if it requires Mapbox MCP tool intervention. | ||
|
|
||
| Classification Criteria: | ||
| - simple_location: Researcher mentions one or two specific places. | ||
| - route_distance: Researcher mentions travel between points or distances. | ||
| - multi_location: Researcher lists many places (e.g., "top 10 museums"). | ||
| - nearby_search: Researcher mentions things "near" a location. | ||
| - complex_operation: Requires multiple steps or logic. | ||
|
|
||
| Response to analyze: | ||
| ${state.researcherResponse}`; | ||
|
|
||
| const { object } = await generateObject({ | ||
| model, | ||
| schema: QueryClassificationSchema, | ||
| prompt: ORCHESTRATOR_CLASSIFICATION_PROMPT, | ||
| }); | ||
|
|
||
| return { classification: object }; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
classifierNode uses generateObject with getModel() which defaults to X.AI grok — this will fail at runtime.
getModel() resolves to X.AI's grok-4-fast-non-reasoning by default, and X.AI's API rejects JSON Schema structured-output arguments. The generateObject call on line 63 will fail with a 400 error, causing the entire graph to hang or crash.
Use a dedicated model known to support structured outputs (e.g., OpenAI gpt-4o) for generateObject calls.
Proposed fix
+import { openai } from "@ai-sdk/openai";
+
const classifierNode = async (state: typeof MapGraphState.State) => {
console.log("--- NODE: CLASSIFIER ---");
- const model = await getModel();
// ...prompt...
const { object } = await generateObject({
- model,
+ model: openai("gpt-4o"),
schema: QueryClassificationSchema,
prompt: ORCHESTRATOR_CLASSIFICATION_PROMPT,
});Based on learnings: "Workers that rely on ai.generateObject must not use the X.AI 'grok-4-fast-non-reasoning' model because X.AI's chat/completions rejects JSON Schema structured-output arguments; prefer OpenAI gpt-4o for structured outputs."
🤖 Prompt for AI Agents
In `@lib/agents/map-langgraph.tsx` around lines 48 - 70, The classifierNode
currently calls generateObject with model = await getModel(), which defaults to
X.AI grok and will reject JSON Schema structured-output; update classifierNode
so generateObject is passed a model that supports structured outputs (e.g.,
OpenAI gpt-4o) instead of the default getModel() result. Concretely, replace the
model assignment used in generateObject (the variable referenced in the
generateObject call inside classifierNode) with a dedicated OpenAI model
selection (or call a helper that returns OpenAI gpt-4o), keeping the rest
(ORCHESTRATOR_CLASSIFICATION_PROMPT, QueryClassificationSchema, generateObject)
intact so structured-output is accepted. Ensure the unique symbols affected are
classifierNode, getModel, generateObject, QueryClassificationSchema, and
ORCHESTRATOR_CLASSIFICATION_PROMPT.
| // Router for conditional edge | ||
| const shouldContinue = (state: typeof MapGraphState.State) => { | ||
| const isSpatial = state.classification?.type !== undefined; | ||
| const hasGeoJSON = state.geojson !== null && state.geojson.features.length > 0; | ||
|
|
||
| if (isSpatial && !hasGeoJSON && (state.iteration || 0) < 2) { | ||
| return "classifier"; | ||
| } | ||
| return END; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldContinue re-classification loop may not produce different results.
When shouldContinue loops back to classifier, the state's researcherResponse is unchanged, so the classifier will likely produce the same classification and MCP operations. If the first attempt failed to produce GeoJSON (e.g., MCP client is absent or geocoding returned no features), the retry will fail identically — burning an iteration cycle with no new information.
Consider adding a condition to skip the retry when mcpClient is absent, or modifying the input to the classifier on retry (e.g., appending feedback about what was missing).
🤖 Prompt for AI Agents
In `@lib/agents/map-langgraph.tsx` around lines 150 - 158, The shouldContinue
router can loop futilely because re-invoking the "classifier" uses the same
MapGraphState.State (researcherResponse unchanged) and will repeat failures if
mcpClient is absent or geocoding produced no features; update shouldContinue to
avoid retrying when mcpClient is missing (check state.mcpClient or equivalent
and return END) or, alternatively, change the classifier input before retry by
setting a flag or appending feedback into state.researcherResponse (e.g., add a
"retryFeedback" note describing "no mcpClient" or "no geojson features") and
only loop back when that feedback has been added so the classifier gets new
context; reference shouldContinue, MapGraphState.State,
state.researcherResponse, state.mcpClient, state.geojson, state.classification,
and state.iteration when making the change.
| .addEdge("mcp_executor", "parser") | ||
| .addEdge("parser", "command_generator") | ||
| .addEdge("command_generator", "validator") | ||
| .addConditionalEdges("validator", shouldContinue as any, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
as any cast on shouldContinue suppresses type checking.
This is a known ergonomic gap in LangGraph's TypeScript conditional-edge API, but worth a // TODO comment explaining why it's needed so future maintainers don't silently propagate the pattern.
🤖 Prompt for AI Agents
In `@lib/agents/map-langgraph.tsx` at line 173, The call to
addConditionalEdges("validator", shouldContinue as any, ...) suppresses type
checking by casting shouldContinue to any; add a concise TODO comment
immediately above this call explaining that the as any cast is required because
of a known LangGraph TypeScript API ergonomic gap in the conditional-edge
signature, reference the symbols addConditionalEdges and shouldContinue, and
include a link or issue number if available and a note to remove the cast once
LangGraph fixes the types so future maintainers understand why the cast exists.
| const stream = await mapGraph.stream(initialState, { streamMode: "values" }); | ||
|
|
||
| let finalResult: any = initialState; | ||
|
|
||
| for await (const update of stream) { | ||
| finalResult = update; | ||
|
|
||
| if (options.onStatusUpdate) { | ||
| if (update.classification && (!update.mcpData || update.mcpData.length === 0)) options.onStatusUpdate("Classifying query..."); | ||
| if (update.mcpData?.length > 0 && !update.geojson) options.onStatusUpdate("Geocoding locations..."); | ||
| if (update.geojson && (!update.commands || update.commands.length === 0)) options.onStatusUpdate("Generating map data..."); | ||
| if (update.commands?.length > 0) options.onStatusUpdate("Finalizing map..."); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
streamMode: "values" means each update is the full accumulated state — verify this assumption holds for your LangGraph version.
The code assigns finalResult = update on each iteration, assuming each streamed value is the complete state snapshot. With streamMode: "values", this should be correct, but the status-update logic (lines 206–209) fires conditions that aren't mutually exclusive — e.g., when commands arrive, geojson is also present, so both the "Generating map data..." and "Finalizing map..." updates could fire in the same tick. Consider using else if for cleaner status progression.
Proposed fix
if (options.onStatusUpdate) {
- if (update.classification && (!update.mcpData || update.mcpData.length === 0)) options.onStatusUpdate("Classifying query...");
- if (update.mcpData?.length > 0 && !update.geojson) options.onStatusUpdate("Geocoding locations...");
- if (update.geojson && (!update.commands || update.commands.length === 0)) options.onStatusUpdate("Generating map data...");
- if (update.commands?.length > 0) options.onStatusUpdate("Finalizing map...");
+ if (update.commands?.length > 0) options.onStatusUpdate("Finalizing map...");
+ else if (update.geojson) options.onStatusUpdate("Generating map data...");
+ else if (update.mcpData?.length > 0) options.onStatusUpdate("Geocoding locations...");
+ else if (update.classification) options.onStatusUpdate("Classifying query...");
}🤖 Prompt for AI Agents
In `@lib/agents/map-langgraph.tsx` around lines 198 - 211, The code assumes
mapGraph.stream(initialState, { streamMode: "values" }) yields full state
snapshots and then calls options.onStatusUpdate multiple times per update;
confirm streamMode is supported and returns complete states in your LangGraph
version, then change the sequential status checks inside the for-await loop (the
block updating finalResult and calling options.onStatusUpdate) to use an
exclusive chain (if / else if / else if / else) so only one status message fires
per streamed update (reference mapGraph.stream, finalResult, update, and
options.onStatusUpdate).
| "@ai-sdk/langchain": "^2.0.82", | ||
| "@ai-sdk/openai": "^1.3.24", | ||
| "@ai-sdk/xai": "^1.2.18", | ||
| "@composio/core": "^0.3.3", | ||
| "@google-cloud/storage": "^7.18.0", | ||
| "@google/generative-ai": "^0.24.1", | ||
| "@heroicons/react": "^2.2.0", | ||
| "@hookform/resolvers": "3.3.4", | ||
| "@langchain/core": "^1.1.19", | ||
| "@langchain/google-genai": "^2.1.15", | ||
| "@langchain/langgraph": "^1.1.4", | ||
| "@langchain/openai": "^1.2.5", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
echo "=== `@langchain/google-genai` usage ==="
rg -rn '@langchain/google-genai' --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' --type ts --type tsx -g '!node_modules' -g '!package.json' -g '!package-lock.json' || echo "NOT FOUND"
echo ""
echo "=== `@langchain/openai` usage ==="
rg -rn '@langchain/openai' --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' --type ts --type tsx -g '!node_modules' -g '!package.json' -g '!package-lock.json' || echo "NOT FOUND"
echo ""
echo "=== `@langchain/core` usage ==="
rg -rn '@langchain/core' --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' --type ts --type tsx -g '!node_modules' -g '!package.json' -g '!package-lock.json' || echo "NOT FOUND"
echo ""
echo "=== `@ai-sdk/langchain` usage ==="
rg -rn '@ai-sdk/langchain' --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' --type ts --type tsx -g '!node_modules' -g '!package.json' -g '!package-lock.json' || echo "NOT FOUND"
echo ""
echo "=== `@langchain/langgraph` usage (for reference) ==="
rg -rn '@langchain/langgraph' --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' --type ts --type tsx -g '!node_modules' -g '!package.json' -g '!package-lock.json' || echo "NOT FOUND"Repository: QueueLab/QCX
Length of output: 367
Remove unused LangChain and ai-sdk packages.
Verification confirms that @langchain/google-genai, @langchain/openai, and @ai-sdk/langchain are not imported anywhere in the codebase and should be removed. Only @langchain/langgraph is actually used in lib/agents/map-langgraph.tsx. Keep @langchain/core (likely a transitive dependency of langgraph) and remove the three unused packages to avoid unnecessary bundle bloat.
🤖 Prompt for AI Agents
In `@package.json` around lines 22 - 33, Remove the three unused dependencies from
package.json: delete the entries for "@langchain/google-genai",
"@langchain/openai", and "@ai-sdk/langchain" (leave "@langchain/langgraph" and
"@langchain/core" intact as langgraph is used in lib/agents/map-langgraph.tsx);
after editing package.json run the package manager install (npm/yarn/pnpm) and
run the test/build to ensure nothing else imports those packages and no
transitive changes break the project.
User description
This commit introduces a new asynchronous agent,
geojsonEnricher, responsible for processing textual responses and extracting structured geospatial data.Key changes:
geojsonEnricheragent inlib/agents/geojson-enricher.tsx.LocationResponse,MapCommand, and GeoJSON objects inlib/types/custom.ts.app/actions.tsx.LocationResponseHandlercomponent to update the map state via theMapDataContext.Mapboxcomponent to render GeoJSON data and execute map commands.PR Type
Enhancement
Description
Add GeoJSON enrichment agent for geospatial data extraction
Integrate map visualization with GeoJSON rendering capabilities
Create location response handler for map state management
Define TypeScript interfaces for geospatial data structures
Diagram Walkthrough
File Walkthrough
custom.ts
Define geospatial TypeScript interfaceslib/types/custom.ts
LocationResponse,MapCommand, and GeoJSON typesactions.tsx
Integrate GeoJSON enrichment into main workflowapp/actions.tsx
geojsonEnricheragentLocationResponseHandlercomponent integrationmapbox-map.tsx
Add GeoJSON rendering and map command executioncomponents/map/mapbox-map.tsx
geojson-enricher.tsx
Create GeoJSON enrichment agentlib/agents/geojson-enricher.tsx
LocationResponseobjectsmap-data-context.tsx
Extend map context with geospatial datacomponents/map/map-data-context.tsx
geojsonandmapCommandsfields to MapData interfacelocation-response-handler.tsx
Create location response handler componentcomponents/map/location-response-handler.tsx
package.json
Update dependencies for geospatial featurespackage.json
@radix-ui/react-select,nanoid, andzoddependenciesQCXdependency referenceSummary by CodeRabbit
New Features
Bug Fixes
Performance
Documentation & Tests