feat: snip a region into chat, and make links clickable (#14)

This commit was merged in pull request #14.
This commit is contained in:
2026-10-05 08:38:50 +00:00
parent f5d1ced4d6
commit 576c5946f7
44 changed files with 3890 additions and 120 deletions
+128 -2
View File
@@ -225,13 +225,139 @@ describe("local tools", () => {
expect(result.content).toContain("Current date and time:");
});
it("describes location as a region rather than an address", async () => {
it("describes location as a region rather than an address when nothing is configured", async () => {
const result = await executeTool("get_location", {}, context);
expect(result.isError).toBe(false);
expect(result.content).toContain("Timezone:");
expect(result.content).toContain("Locale:");
expect(result.content).toContain("not a precise address");
expect(result.content).toContain("no Location is set in preferences");
expect(result.content).toContain("Ask which city they are in");
});
it("never claims a city derived from the timezone name", async () => {
// `Asia/Manila` is one zone covering the whole Philippines, so reporting
// "City: Manila" was wrong for anyone in Cebu and confidently so.
const result = await executeTool("get_location", {}, context);
expect(result.content).not.toMatch(/^City:/m);
expect(result.content).toContain("The timezone area is the timezone's own name, not the user's city.");
});
it("puts the configured place first and says to prefer it", async () => {
const result = await executeTool("get_location", {}, {
...context,
place: "Cebu City, Philippines",
});
expect(result.content).toContain("Place: Cebu City, Philippines");
expect(result.content).toContain("prefer it");
expect(result.content).not.toContain("no Location is set");
expect(result.content.indexOf("Place:")).toBeLessThan(result.content.indexOf("Timezone:"));
});
it("still reports the timezone alongside a configured place, for local time questions", async () => {
const result = await executeTool("get_location", {}, { ...context, place: "Cebu City" });
expect(result.content).toContain("Timezone:");
expect(result.content).toContain("UTC offset:");
});
});
describe("get_location with IP geolocation", () => {
const resolved = {
status: "ok" as const,
place: {
place: "Cebu City, Central Visayas, Philippines",
city: "Cebu City",
region: "Central Visayas",
country: "Philippines",
timezone: "Asia/Manila",
source: "ipwho.is",
fetchedAt: 1_000,
},
};
it("reports the resolved place and says how approximate it is", async () => {
const ipPlace = vi.fn(async () => resolved);
const result = await executeTool("get_location", {}, { ...context, ipPlace });
expect(result.isError).toBe(false);
expect(result.content).toContain("Place: Cebu City, Central Visayas, Philippines");
expect(result.content).toContain("rough guess");
expect(result.content).toContain("ipwho.is");
});
it("does not look the address up at all when the user has said where they are", async () => {
// The preference is both more accurate and free, so sending the IP anyway
// would be a privacy cost for nothing.
const ipPlace = vi.fn(async () => resolved);
const result = await executeTool("get_location", {}, {
...context,
place: "Cebu City, Philippines",
ipPlace,
});
expect(ipPlace).not.toHaveBeenCalled();
expect(result.content).toContain("Place: Cebu City, Philippines");
expect(result.content).not.toContain("ipwho.is");
});
it("surfaces a failed lookup instead of quietly serving the region answer", async () => {
const ipPlace = vi.fn(async () => ({ status: "failed" as const, message: "ipwho.is answered HTTP 429" }));
const result = await executeTool("get_location", {}, { ...context, ipPlace });
expect(result.content).toContain("IP geolocation is switched on but the lookup failed");
expect(result.content).toContain("HTTP 429");
// The region answer is still there, so the tool stays useful.
expect(result.content).toContain("Timezone:");
});
it("does not blame an unset preference when the lookup itself failed", async () => {
// These are different problems and send the user to different places. The
// original wording said "no Location is set in preferences" for both, which
// is exactly what made a broken lookup look like a missing setting.
const ipPlace = vi.fn(async () => ({ status: "failed" as const, message: "ipwho.is answered HTTP 429" }));
const result = await executeTool("get_location", {}, { ...context, ipPlace });
expect(result.content).not.toContain("no Location is set in preferences");
});
it("tells the model to pass a failed lookup on, since it is the only channel to the user", async () => {
const ipPlace = vi.fn(async () => ({ status: "failed" as const, message: "offline" }));
const result = await executeTool("get_location", {}, { ...context, ipPlace });
expect(result.content).toContain("Tell the user");
expect(result.content).toContain("Setting Location in preferences avoids the lookup");
});
it("flags an IP timezone that disagrees with the clock, which usually means a VPN", async () => {
const ipPlace = vi.fn(async () => ({
status: "ok" as const,
place: { ...resolved.place, timezone: "America/New_York" },
}));
const result = await executeTool("get_location", {}, { ...context, ipPlace });
expect(result.content).toContain("differs from the device timezone");
});
it("says nothing about a mismatch when the timezones agree", async () => {
const ipPlace = vi.fn(async () => resolved);
const result = await executeTool("get_location", {}, { ...context, ipPlace });
// The test host's own zone varies, so this asserts the absence of the note
// only for the case where the provider echoed the same value.
if (result.content.includes("IP-reported timezone")) {
expect(result.content).toContain("differs");
} else {
expect(result.content).not.toContain("IP-reported timezone");
}
});
});