feat: opt-in IP geolocation for get_location
Adds the one thing the extension could not answer before: roughly where the user actually is, rather than which zone their clock is set to. - `lib/geo.ts` resolves a place from ipapi.co. Chosen because it is HTTPS and needs no API key; ip-api.com, the other obvious free option, is HTTP-only on its free tier, which is not a place to send an IP address. - Off by default, and skipped entirely when the user has set Location: the better answer is already there, so spending their privacy on a worse one would be indefensible. `ipPlace` is only wired into ToolContext when the preference is on, so an off setting cannot reach the network at all. - Cached for 6 hours. A city does not change between questions, and every lookup hands the provider another copy of the address. Set Up Lam Agent clears it on re-test, alongside the vision cache. - Failures are returned, not swallowed: a rate limit or an offline machine says so in the tool output while the region answer stays available. The provider signals some failures with error: true and HTTP 200, so the body is checked as well as the status. - The tool output always names the provider and calls the result city-level rather than exact. It also flags an IP timezone that disagrees with the device clock, which usually means a VPN. - The README claimed no geolocation service ever saw the user's address. That is now conditional, so the section was rewritten rather than left to quietly become untrue. Deliberately not done: GPS. CoreLocation has no CLI on macOS and GeoClue's usual provider resolves nearby Wi-Fi through a Mozilla service anyway. Verified: 341 tests (25 new), tsc, eslint, prettier, and vici build. I did not make a live API call, because doing so would send this machine's IP to the provider as the exact flow being opted into.
This commit is contained in:
@@ -2,6 +2,7 @@ import { Action, ActionPanel, Icon, List, openExtensionPreferences } from "@vici
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { describeResumeTimeout, getLamPreferences, needsSetup } from "./lib/preferences";
|
||||
import { listModels } from "./lib/ollama";
|
||||
import { forgetGeoPlace } from "./lib/geo";
|
||||
import { forgetVisionSupport, visionSupport, type VisionSupport } from "./lib/vision";
|
||||
import type { ModelInfo } from "./lib/types";
|
||||
import { errorMessage } from "./lib/utils";
|
||||
@@ -60,6 +61,10 @@ export default function SetupCommand() {
|
||||
// re-pull, a retagged build), so re-testing drops the cached answer that the
|
||||
// chat view gates the snip action on rather than leaving a stale "no".
|
||||
await forgetVisionSupport();
|
||||
|
||||
// Same reasoning for the IP-derived place: a re-test is the moment to stop
|
||||
// trusting an answer that was resolved hours ago, possibly in another city.
|
||||
await forgetGeoPlace();
|
||||
setVision(
|
||||
await visionSupport({
|
||||
host: preferences.host,
|
||||
@@ -176,6 +181,18 @@ export default function SetupCommand() {
|
||||
}
|
||||
actions={settingsActions}
|
||||
/>
|
||||
<List.Item
|
||||
icon={preferences.place || preferences.ipGeolocation ? Icon.Person : Icon.QuestionMarkCircle}
|
||||
title="Location"
|
||||
subtitle={
|
||||
preferences.place
|
||||
? `${preferences.place} (from preferences)`
|
||||
: preferences.ipGeolocation
|
||||
? "Rough city, resolved from the IP address via ipapi.co"
|
||||
: "Region only, from the timezone and locale"
|
||||
}
|
||||
actions={settingsActions}
|
||||
/>
|
||||
</List.Section>
|
||||
|
||||
{error ? (
|
||||
|
||||
Reference in New Issue
Block a user