fix: route plan_route stops by resolved waypoints and add text-search fallback - #87
Merged
Merged
Conversation
… fallback planRoute resolved every stop twice, with two different resolvers, and then zipped the results by array index: the display labels came from the Geocoding API, while the distances came from the Routes API independently re-resolving the same raw strings. When the two disagreed, the tool returned success and its legs described a different place than its own labels. Resolve each stop once and pass the result to computeRoutes as a structured place ID waypoint, falling back to coordinates when the resolved place has no place ID. Routes then routes to the same place the label names, so the two can no longer diverge. Google recommends place IDs over coordinates and address strings for exactly this reason: they are unambiguous and let Routes use the place's own access points, while coordinates are snapped to the nearest road. Coordinate stops keep the caller's exact point and no longer depend on the Geocoding API at all: the point needs no resolving, so geocoding is used only to fetch a display address and its failure is not fatal. A coordinate-shaped string outside valid latitude/longitude range now fails immediately, without an API call, instead of being text-searched. Also add a Places Text Search fallback for input Geocoding cannot resolve. The failing class is non-address free text, such as "精進湖 他根浜", which previously threw in both planRoute and exploreArea; landmark names geocode normally and are unaffected. computeRoutes takes optional originLabel/destinationLabel so its no-route and no-transit errors keep naming the stop the caller asked for rather than the resolved place ID. No tool schema changed, so the tool checklist in CLAUDE.md does not apply.
weikhang95
force-pushed
the
feat/route-waypoint-resolution
branch
from
September 11, 2026 08:32
6ea5090 to
1955128
Compare
The Routes API rejects waypoint optimization requests with a single intermediate stop, so only enable `optimizeWaypointOrder` when there are more than 3 stops (2+ intermediates). Also add unit tests for `planRoute` covering 2, 3 and more stops with optimization enabled and disabled, using stubbed geocoding and Routes API responses.
Owner
|
Thanks for the exceptionally detailed report and verification — especially the concrete label/route divergence example, the fallback cost note, and the follow-up scope. I’ve updated this branch as maintainer to integrate #86 while preserving both contributors’ commits. The PR now also:
CI is now green. I intentionally left location biasing and the related raw-string paths in |
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PlacesSearcher.planRouteresolved every stop twice, with two different resolvers, then zipped the results by array index.geocoded[]came from the Geocoding API and supplied only the display labels; the raw strings went separately tocomputeRoutes, wheretoWaypointturned any non-lat,lngstring into{ address: rawString }— a second, independent geocode. Google's Specify locations for a route says an address string "must first be geocoded by the Routes API" and that the result may differ from the Geocoding API.So labels and distances could describe different places while the tool returned
success: true. Onmain(v0.0.55):12158.8 ≠ 20251.4— Routes had resolvedSAWA Hotelto a third place, so the agent seessuccessand reports a route nobody measured.plan_routeandexplore_areaalso threw on free text Geocoding cannot resolve, even when Text Search finds it:The class is narrow: text that is neither an address nor an indexed landmark.
Tokyo Towergeocodes fine andexplore_area("Tokyo Tower")works onmain.Fix
PlacesSearcher.resolveLocation(input)resolves a string once —lat,lngparsed and range-checked locally, otherwise Geocoding, then Places Text Search, throwing if neither resolves. It throws rather than returning{ success: false }becauseplanRoute.tsandexploreArea.tsJSON.stringify(result.data)without checking the flag.The result reaches
computeRoutesas a place ID waypoint, so label and route cannot disagree. Google recommends place IDs "instead of latitude/longitude coordinates or address strings", because a coordinate is "snapped to the road nearest to those coordinates".Changes
src/services/RoutesService.tsCOORDINATE_STRING_PATTERN; addRouteWaypoint/WaypointInputsoorigin/destination/intermediatesaccept structured waypoints; adddescribeWaypoint; optionaloriginLabel/destinationLabelso no-route errors still name the caller's stopsrc/services/PlacesSearcher.tsResolvedLocation,toRouteWaypoint,resolveLocation;planRouteandexploreArearesolve each input oncetests/locationResolution.unit.test.tstests/routesWaypoints.unit.test.tsNo tool schema, description or registration changed, so the 9-file tool checklist in
CLAUDE.mddoes not apply.computeRouteMatrixis untouched and still takes strings.Impact
SAWA Hotelstill lands in Cameroon — now consistently rather than divergently.plan_route/explore_area;toWaypointis unchanged, soget_directionsanddistance_matrixbehave as today.Failed to geocode: X→Failed to resolve location: X (geocoding: …; places text search: …).geocodeflattens every error into{ success: false }with no status code. Text Search is the pricier call ($32.00 vs $5.00 per 1000 at the first paid tier).optimize: truefails withCannot read properties of undefined (reading 'originalName')on this branch and onmainalike — the bug fix: require at least 2 intermediates for waypoint optimization #86 fixes, in the same index-zip code the divergence above came from.Test plan
npm run buildsucceedsnpm run lint— 0 errors (175 pre-existing warnings, plus 22no-explicit-anyon the new tests' stubs)npm test— 198 passed, 0 failednpm run test:unit— 16 passed, 0 failed, withtests/exploreArea.unit.test.tsuneditedsrc/tomainand keeping the new tests: 5 pass, 11 fail — the 11 are exactly the new assertionsVerification
plan_route(["Tokyo Tower","SAWA Hotel"])place_idplan_route(["河口湖駅","精進湖 他根浜"])Failed to geocodeexplore_area("精進湖 他根浜")Failed to geocode addressplan_route([2 coordinate strings])Follow-ups (not in this PR)
get_directions,search_along_routeanddistance_matrixstill pass raw strings to Routes, with the same divergence exposure.npm run test:unitis not in CI. I leftci.ymlalone deliberately; say the word and I will send the one-line PR.Changelog entry