fix: stop reporting a timezone's name as the user's city

get_location printed "City: Manila", derived from the last segment of the
IANA zone name. That is the timezone database's representative place, not
the user's: Asia/Manila is one zone covering the entire Philippines,
Asia/Kolkata covers all of India, Europe/Oslo all of Norway. It was wrong
for anyone outside the zone's namesake, and confidently so, because a model
asked about the weather uses it without hesitating. The README claimed the
value was region-level, so the output also contradicted its own docs.

- The zone's label is still reported, as `Timezone area`, and named for what
  it is. Both the field and the tool note say outright that it is not the
  user's city, and the tool description repeats it.
- The locale region subtag is now used, which is a declaration rather than an
  inference: a locale of en-PH is the Philippines because the user set it.
- A Location preference was added. It is the only exact place this extension
  has, it is reported verbatim and first, and when it is set the note tells
  the model to prefer it over anything inferred.
- The continent grouping from the zone name was dropped: "Asia" next to a
  region of "Philippines" reads as two answers to the same question.
- ToolContext takes the place as an injected value, keeping tools.ts free of
  Vicinae imports as the module wiring test requires.

Verified: 316 tests, tsc, eslint, prettier, and vici build.
This commit is contained in:
2026-10-04 15:50:45 +08:00
parent 35d88f635c
commit 400f81fa6c
9 changed files with 225 additions and 35 deletions
+10
View File
@@ -15,6 +15,7 @@ export interface LamPreferences {
model?: string;
ollamaHost?: string;
systemPrompt?: string;
location?: string;
webSearchEnabled?: boolean;
thinking?: string;
showToolCalls?: boolean;
@@ -28,6 +29,14 @@ export interface ResolvedPreferences {
model: string;
host: string;
systemPrompt: string;
/**
* The place the user says they are, verbatim, or an empty string.
*
* The only exact location this extension has. Everything else is inferred from
* the device timezone and locale, which is accurate to a region rather than a
* city, and a timezone name is not a city at all.
*/
place: string;
webEnabled: boolean;
/** Undefined when reasoning is off, which is what the API client expects. */
think?: string;
@@ -78,6 +87,7 @@ export function resolvePreferences(raw: LamPreferences): ResolvedPreferences {
model: (raw.model ?? "").trim() || DEFAULT_MODEL,
host: (raw.ollamaHost ?? "").trim() || DEFAULT_HOST,
systemPrompt: (raw.systemPrompt ?? "").trim(),
place: (raw.location ?? "").trim(),
// Defaults to on: the preference is a checkbox the user has to actively untick.
webEnabled: raw.webSearchEnabled ?? true,
think: thinking && thinking !== "off" ? thinking : undefined,