fix: ipapi.co stopped answering, so the lookup never resolved

Turning IP Geolocation on changed nothing: every lookup failed, and the
tool output still said "no Location is set in preferences", which is a
different problem and sent the user looking in the wrong place.

ipapi.co no longer serves an unauthenticated caller. Verified directly:

  no User-Agent    -> HTTP 429 {"reason":"RateLimited",
                                 "message":"Please sign up for a paid plan"}
  browser UA       -> HTTP 403, a Cloudflare "Just a moment..." challenge

So the provider is now ipwho.is, which answers 200 without a key. It
signals refusal with success: false on an HTTP 200, so the body is checked
as well as the status, and it nests the timezone under an object, which the
parser now accepts alongside a bare string. A named User-Agent is sent:
node's default is bare, and "who is asking" is the one thing a geolocation
provider has a fair interest in.

The failure branch is also fixed. A failed lookup and an unset preference
are different problems, and the wording no longer conflates them. The note
tells the model to pass the failure on, because the model is the only
channel that reaches the user and it had been paraphrasing the failure away.

Accuracy is documented honestly rather than as "city-level". An IP locates
the ISP's egress, not the person: measured from one machine, an IPv4 query
resolved to Cebu City and an IPv6 query moments later to Talisay in
Calabarzon, about 500km apart. That is now in the README, the preference
description and the tool output, and it is the argument for setting
Location instead.

Verified live with the exact request and field access the extension uses:
HTTP 200, "Talisay, Calabarzon (Region IV-A), Philippines", Asia/Manila.
344 tests, tsc, eslint, prettier, and vici build.
This commit is contained in:
2026-10-04 15:59:09 +08:00
parent 51c16b678f
commit af325b4a62
7 changed files with 102 additions and 39 deletions
+8 -2
View File
@@ -123,7 +123,7 @@ vicinae 'vicinae://launch/lam-agent/chat?fallbackText=summarise%20this'
| **Model** | None | Required. Any model your key can reach. |
| **Ollama Host** | `https://ollama.com` | Set to `http://localhost:11434` for a local server. |
| **Location** | None | The place Lam should treat as yours, for example `Cebu City, Philippines`. Optional. The only exact location available, and the only one that stops anything being inferred. |
| **IP Geolocation** | off | Off by default. When on, and only when **Location** is empty, your IP is sent to ipapi.co to resolve a rough city. |
| **IP Geolocation** | off | Off by default. When on, and only when **Location** is empty, your IP is sent to ipwho.is to resolve a rough area. |
| **System Prompt** | Lam's default | Replaces the built-in prompt entirely. The default asks for plain prose: no em dashes, no generated-text tells, no restating the question. |
| **Web Tools** | on | Enables `web_search` and `web_fetch`. |
| **Reasoning Effort** | Off | `low`/`medium`/`high` for models that support thinking. |
@@ -148,10 +148,16 @@ Lam calls these on its own when they would improve an answer. All four are read-
The **region** comes from your locale, which is a declaration rather than an inference: a locale of `en-PH` says the Philippines because that is what you set. It costs nothing and it cannot be wrong the way a zone name can.
If you turn on **IP Geolocation**, and only then, `get_location` resolves a rough city from your IP address via [ipapi.co](https://ipapi.co). That is a third party learning approximately where you are, so it is off by default, it is skipped entirely whenever you have set a Location (the better answer is already there, and there is no reason to spend your privacy on a worse one), and the tool output names the provider and says the result is city-level rather than exact. The answer is cached for 6 hours rather than looked up per question, because a city does not change between questions and every lookup hands over another copy of your address. `Set Up Lam Agent` clears both that cache and the capability cache when you re-test.
If you turn on **IP Geolocation**, and only then, `get_location` resolves a rough area from your IP address via [ipwho.is](https://ipwho.is). That is a third party learning approximately where you are, so it is off by default, it is skipped entirely whenever you have set a Location (the better answer is already there, and there is no reason to spend your privacy on a worse one), and the tool output names the provider and calls the result a rough guess rather than a city.
**It is coarser than "city-level" suggests, and that is worth knowing before trusting it.** An IP address identifies the point where the traffic left the ISP, not the person. Measured from one machine on Globe Telecom, an IPv4 lookup resolved to Cebu City and an IPv6 lookup moments later resolved to Talisay in Calabarzon, roughly 500km apart. If you want a place you can rely on, set **Location** and turn this off.
The answer is cached for 6 hours rather than looked up per question, because an area does not change between questions and every lookup hands over another copy of your address. `Set Up Lam Agent` clears that cache, and the capability cache, when you re-test.
This extension does not use GPS. That would mean CoreLocation on macOS, which has no command-line interface, or GeoClue on Linux, whose usual provider resolves nearby Wi-Fi networks through a Mozilla service anyway. Neither is worth it for "is it late where you are".
**The provider has to be one that answers an unauthenticated caller.** The first choice was ipapi.co, which no longer does: a request with no User-Agent now returns `HTTP 429` with `"Please sign up for a paid plan"`, and adding a browser User-Agent gets an `HTTP 403` Cloudflare bot challenge instead. ipwho.is answers without a key and signals failures with `success: false` on an `HTTP 200`, so the body is checked as well as the status. `GEO_PROVIDER` is a single constant, and swapping it means changing the parser beside it.
Web search results and fetched pages can be large. Lam truncates them before they reach the model, and gives up after 5 rounds of tool calls rather than looping.
## Architecture