Skip to content

🌤️ fix: Migrate OpenWeather Tool to One Call API 4.0 - #16081

Open
gokay-ai wants to merge 9 commits into
LibreChat-AI:devfrom
gokay-ai:cursor/openweather-one-call-4-1fa1
Open

gokay-ai wants to merge 9 commits into
LibreChat-AI:devfrom
gokay-ai:cursor/openweather-one-call-4-1fa1

Conversation

@gokay-ai

@gokay-ai gokay-ai commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A new OpenWeather key 401s on every tool call: the tool still hits https://api.openweathermap.org/data/3.0, and OpenWeather no longer sells One Call 3.0. After this change the tool uses One Call API 4.0 by default and maps the split 4.0 payloads back onto the existing current/hourly/daily, timestamp data[], daily-aggregation temperature, and overview weather_overview contract so agents keep the same fields.

Existing One Call 3.0 subscriptions are still valid — 4.0 is a separate product. Operators with a 3.0-only key can set OPENWEATHER_ONECALL_VERSION=3.0 (documented in .env.example). Default stays 4.0 for new keys.

Fixes #16064

How it works

OpenWeather._call
  executeOpenWeather                    # version from OPENWEATHER_ONECALL_VERSION (default 4.0)
    geocodeCity                         # geo/1.0/direct, unchanged
    3.0 current_forecast|timestamp|…    # /data/3.0/onecall* when opted in
    4.0 current_forecast                # /current + timeline/1h (20h, one page) + /1day
      Promise.allSettled                # keep successful parts; note failures
      resolve alerts                    # /onecall/alert/{id} only when IDs are present
    timestamp                           # timeline/1h?start= (tz-aware local midnight)
    daily_aggregation                   # timeline/1day → temperature.morning/afternoon/evening/night
    overview                            # synthesized from current or daily (4.0 has no /overview)

next/prev pagination URLs are dropped so the API key never reaches the model. Hourly pages are followed only on https://api.openweathermap.org/data/4.0/onecall/, and by default we stop after one page (at most 20 hourly records, the documented 1h response cap). Alert detail IDs keep the fixed origin/path and are encodeURIComponent'd; colons and other opaque identifier characters are allowed, while /, .., and encoded path separators are rejected. 4.0 alert description arrays ({ language, description }) are flattened so the model still receives the hazard text. A requested daily date with no matching record returns no-data instead of records[0].

One Call 4.0 bills per HTTP call. current_forecast went from 1 billed call (3.0) to several; the default path is now current + hourly + daily (about 3 calls plus geocode), not 4–7. Minute precipitation is skipped unless exclude includes +minutely.

Type of change

  • Bug fix

As the code stands:

  • Default One Call version is 4.0; OPENWEATHER_ONECALL_VERSION=3.0 keeps a working 3.0 subscription on /data/3.0.
  • Hourly forecast is one page (up to 20h) by default.
  • Minute forecast is not fetched unless requested.
  • current_forecast keeps successful 4.0 parts when another endpoint 401s/429s.
  • Alert IDs accept strings or numbers, including documented opaque IDs with colons; they are resolved only when present (exclude=alerts skips that).
  • 4.0 multilingual alert descriptions are normalized onto description (string fixtures still work).
  • daily_aggregation maps temp.morn/day/eve/night and leaves 4.0-missing period humidity/cloud/pressure/wind undefined. A missing requested date is no-data, not another day's record.
  • Date-based actions honour tz (IANA name or ±HH:MM) as the local day boundary.
  • Synthesized overview temperatures are rounded to the nearest degree.

In-repo OpenWeather docs live in .env.example and the tool help payload (the public docs site is a separate repo).

Testing

  • cd packages/api && npx jest src/tools/weather --coverage=false — 43 passed
  • cd packages/api && npx jest src/telemetry/sdk.spec.ts --coverage=false — 26 passed
  • cd api && npx jest app/clients/tools/structured/specs/openweather.test.js --coverage=false — 13 passed
  • packages/api npx tsc --noEmit after building data-provider and data-schemas — clean
  • npm run static-checks -- --against origin/dev — ESLint, Prettier, import sorting passed

Test Configuration:

Node v24.16.0. Fixtures/mocks only; no live OpenWeather key.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

OpenWeather no longer sells One Call 3.0, so a new API key 401s on the
hardcoded data/3.0 endpoints. Call 4.0 instead and map its split
payloads back onto the existing current/hourly/daily, timestamp, daily
aggregation, and overview contract.

Signed-off-by: GokayAI <60583610+gokay-ai@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 18, 2026 15:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Static checks failed on ESLint (prettier/prettier) and Prettier --check
for six formatting violations across the weather service, its specs,
and the api-side tool spec. Applied eslint --fix; no logic changes.

@lia-by-librechat lia-by-librechat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pushed 58a552566 to this branch to fix the red Static checks lane: ESLint (prettier/prettier) and Prettier --check were failing on six formatting violations in service.spec.ts, normalize.ts, normalize.spec.ts, and api/.../specs/openweather.test.js. eslint --fix only, no logic touched. Everything else on the previous head was already green.

Review below.

What checks out

I verified the endpoint surface against OpenWeather's own 4.0 docs rather than trusting the diff: /data/4.0/onecall/current, /data/4.0/onecall/timeline/1min, /timeline/1h, /timeline/1day are the real paths, start and cnt are real parameters, geo/1.0/direct is unchanged, and data.alerts really does carry alert IDs with a separate /data/4.0/onecall/alert/{alertId} endpoint for detail. The mapping in normalize.ts matches the documented 3.0 → 4.0 field moves.

Two things I want to call out as good: stripping next/prev before serialising (those URLs embed appid, so returning them would hand the API key to the model) and constraining followed pagination to https://api.openweathermap.org/data/4.0/onecall/, both with tests. And moving the logic into packages/api/src/tools/weather behind a thin LangChain wrapper, with 49 unit tests on fixtures, is the right shape for this file.

Blocking-ish

1. Legacy 3.0 keys now break, and the PR assumes they cannot exist. The body says "no 3.0 fallback; that product is gone", but OpenWeather's launch note is explicit that existing One Call 3.0 subscriptions keep working and that 4.0 must be subscribed to separately. #16064 is about new keys not being able to buy 3.0. So this turns a currently-working deployment (3.0 subscription, no 4.0) into 401s on every call: the same bug, mirrored. An env escape hatch (OPENWEATHER_ONECALL_VERSION, default 4.0) or a 401-triggered retry against 3.0 would make the fix non-breaking in both directions. This is my main ask.

2. current_forecast went from 1 billed API call to 4–7. current + 1min + 1h (up to 3 pages) + 1day, plus the geocode. One Call by Call bills per call and the free daily allowance counts calls, so identical user traffic now costs roughly 5x and exhausts the free tier ~5x faster. Nothing in the tool or the docs tells the operator that. Two cheap mitigations: do not fetch 1min unless it was asked for (60 minutes of precipitation is rarely what an agent needs, and it is a whole extra billed call on every weather question), and stop at 24 hours by default so hourly is one page instead of three. At minimum this belongs in the PR body and the OpenWeather docs page.

3. Promise.all makes any one endpoint failure fail the whole action. 4.0 is a six-endpoint family; if the key's plan does not cover 1min, or that single endpoint 429s, current_forecast returns a bare error string and throws away the current conditions it successfully fetched. Promise.allSettled, keeping what succeeded and noting what did not, is both more robust and directly in the spirit of the 401 bug this PR fixes.

Correctness

4. Alerts silently disappear if IDs are not strings. asStringArray filters to typeof item === 'string'. The docs say "array of weather alert IDs" without pinning the JSON type; if OpenWeather emits numbers, every alert is dropped with no error and no log. Accept Array<string | number> and stringify. Separately, an agent asked "any weather warnings?" now gets opaque IDs where 3.0 gave it full government alert objects — resolving them through /onecall/alert/{alertId} (only when alerts is non-empty, so no extra cost in the common case) would close the real regression.

5. daily_aggregation fabricates per-period precision. humidity.morning/afternoon/evening/night all receive the same single daily humidity; cloud_cover.afternoon, pressure.afternoon and wind.max likewise present a daily value under a name that claims something narrower. The shape survives, but the model cannot tell which numbers are measurements and which are the same number repeated four times. I would leave the fields 4.0 cannot fill as undefined (they drop out of the JSON) rather than fill them with a relabelled daily mean. The temp.morn/day/eve/night → morning/afternoon/evening/night mapping is legitimate and should stay.

6. Day boundaries are UTC now, silently. convertDateToUnix produces UTC midnight and selectDailyRecord matches on utcDateString(record.dt), falling back to records[0]. 3.0's day_summary took tz and aggregated the local day. For a non-UTC location the fallback can return a different day's record, still stamped with the requested date in the output. Also tz is still advertised in openWeatherSchema but is now ignored entirely, so the model will keep sending a parameter that does nothing: either honour it or drop it from the schema.

7. The synthesized overview bypasses rounding. synthesizeOverview builds the sentence from raw record.temp before stringifyResult runs roundTemperatures over the object, so the text carries decimals. normalize.spec.ts encodes this: 'Temperatures range from 7.4°F to 21.9°F.'. Every other action rounds, and the help payload still promises "All temperatures are rounded to the nearest degree".

Minor

  • EXCLUDE_PARTS accepts alerts, but exclude=alerts has no effect now that alerts ride inside records.
  • minutely is fetched with no cnt and no pagination, so 3.0's 60 minutes probably becomes one server-default page. Pin cnt explicitly, or drop the endpoint per point 2.
  • stripPagination in timestampForecast is a no-op: fetchTimeline already returns a fresh object with no next/prev.
  • OPEN_WEATHER_TOOL_DESCRIPTION lives in service.ts while openWeatherSchema stays in registry/definitions.ts, which now imports from ~/tools/weather while tools/index.ts re-exports both. The circular-dependency lane is green, but keeping the description next to the schema would avoid the barrel edge entirely.
  • api/app/clients/tools/structured/specs/openWeather.integration.test.js was not updated. It happens to still hold (it no-ops without a key, and the fields it asserts survive), but its overview case now asserts a synthesized sentence rather than OpenWeather's generated summary, which is worth a comment so the next reader is not misled.
  • roundTemperatures mutates its argument and returns it; harmless on a freshly parsed object, but the signature reads pure. Pre-existing.

OpenWeather still supports existing 3.0 subscriptions; 4.0 is a separate
product. Default remains 4.0 for new keys, with OPENWEATHER_ONECALL_VERSION=3.0
as an explicit escape hatch.

On 4.0, current_forecast no longer fetches 1min unless asked, stops hourly
pagination at 24h (one page), keeps successful parts via Promise.allSettled,
resolves alert IDs, honours tz for local day boundaries, and leaves 4.0-missing
daily fields undefined instead of fabricating them.

Signed-off-by: GokayAI <60583610+gokay-ai@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/openweather-one-call-4-1fa1 branch from 58a5525 to 8a76281 Compare September 21, 2026 06:46
When every current_forecast endpoint fails, return the original OpenWeather
status error instead of prefixing it with the first part name.

Signed-off-by: GokayAI <60583610+gokay-ai@users.noreply.github.com>

@lia-by-librechat lia-by-librechat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-reviewed exact head aabf77660565cae5896ea6f6b73513e36a92e79c. Thank you for the follow-up. The explicit 3.0 switch, partial-result handling, omitted fabricated daily fields, overview rounding, and exclude=alerts handling address the earlier concerns.

Four remaining findings are inline. The hourly-page recommendation in my earlier review was incorrect; I own that correction. The numeric-ID concern from that review was hypothetical, not an observed provider failure. This round uses the official payload examples and focused reproductions.

Verification: weather + telemetry tests: 63 passed. Four additional temporary regression reproductions fail on the four cases below. Changed-file ESLint and Prettier pass. Local packages/api tsc --noEmit fails on shared dependency/type mismatches and missing zod-openapi outside the weather changes; the exact-head CI TypeScript check passes. Static checks and all test shards are green; production runtime smoke is still pending at posting. No live-key API calls, local JS wrapper test, or full local static-checks runner in this review. No production code changed or pushed.

Graph scope: fork head is not indexed; dependency lookup used dev at fe79265. Findings were read and reproduced from the checked-out PR head, not inferred from that graph.

Comment thread packages/api/src/tools/weather/service.ts Outdated
Comment thread packages/api/src/tools/weather/service.ts Outdated
Comment thread packages/api/src/tools/weather/service.ts Outdated
Comment thread packages/api/src/tools/weather/normalize.ts
Normalize 4.0 multilingual alert descriptions from the official payload,
allow opaque alert IDs that contain colons while still rejecting path
traversal, request at most 20 hourly records per billed page, and return
no-data when a requested daily date is missing instead of records[0].

Signed-off-by: GokayAI <60583610+gokay-ai@users.noreply.github.com>
The day-summary fixture used UTC midnight, which is the previous local
day in America/New_York. With no records[0] fallback, the record must
carry the requested local date.

Signed-off-by: GokayAI <60583610+gokay-ai@users.noreply.github.com>
@gokay-ai

Copy link
Copy Markdown
Contributor Author

Addressed the four review notes in 81fcd32 — multilingual alert descriptions, opaque alert IDs with encodeURIComponent, 20-record hourly page, and no daily date fallback to records[0]. Ready for another look when you have a moment.

@lia-by-librechat lia-by-librechat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed exact head 81fcd32d46717f7b1c25a03bcaab927ff34b53a6. The four previous findings are resolved. One remaining data-integrity issue prevents my approval.

I re-ran the previous reproductions: multilingual alert text survives, the opaque colon-containing ID resolves, the documented one-page contract is now 20 records, and missing dates no longer substitute another day. All four pass. The author fixed what was requested; the precipitation mapping below is something I missed in the earlier rounds, not an incomplete response to those threads.

The broader source review covered the JS wrapper and registry/barrel exports, injected per-call credentials, both product-version paths, concurrent forecast requests and partial/all failures, alert deduplication/resolution/exclusion, pagination bounds, date selection, normalization, and serialized model output. There are no new database/persistence writers to migrate. Request cancellation/timeout/retry behavior remains limited; no live network behavior was validated. Graph dependency evidence is from indexed dev fe03f124f0a83372832a72692136ce19d9b1baa6, not this unindexed fork head. Findings and tests use the actual checked-out head.

Verification

Check Result
Weather + telemetry suites 69 passed
JS OpenWeather wrapper against this head's locally built API (explicit Jest package mapping) 13 passed
Previous diagnostic reproductions, adjusted to the accepted 20-record contract 4 passed
New daily precipitation contract reproduction 1 failed, confirming the inline finding
Local packages/api tsdown build Passed
Local static-checks against PR merge base ESLint, Prettier, import sorting, package.json validation, circular dependencies passed
CI at this head 23 passed, 5 skipped, none pending/failing
Local packages/api tsc --noEmit Failed on shared dependency/type mismatches outside the weather changes; CI TypeScript passed

Not run locally: live-key OpenWeather integration, full repository suites, static-checks --full (config migration, unused i18n/dependency gates). Local diagnostic files only; no production edits, commits, pushes, or merge.

Comment thread packages/api/src/tools/weather/normalize.ts Outdated
One Call 4.0 1-day rain.1h / snow.1h are mm/h rates, not a daily
accumulation. daily_aggregation no longer copies those rates into
precipitation.total; the field is omitted unless a documented daily
total exists.

Signed-off-by: GokayAI <60583610+gokay-ai@users.noreply.github.com>
Signed-off-by: GokayAI <60583610+gokay-ai@users.noreply.github.com>
@gokay-ai

Copy link
Copy Markdown
Contributor Author

Follow-up to your 2026-09-22 review: the remaining precipitation finding is addressed in 6b5eef08 / b9e3581d — hourly rain['1h'] / snow['1h'] rates are no longer copied into daily_aggregation.precipitation.total (field omitted unless a real daily accumulation exists), with coverage for rain-only, snow-only, mixed, and unavailable totals.

Ready for another look when you have a moment.

Keep both the weather and compare tool exports.

Signed-off-by: GokayAI <60583610+gokay-ai@users.noreply.github.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants