























@@ -10,10 +10,9 @@ type TestModelProviderConfig = NonNullable<
10101111function installGeminiFetch() {
1212const mockFetch = vi.fn((_input?: RequestInfo | URL, _init?: RequestInit) =>
13-Promise.resolve({
14-ok: true,
15-json: () =>
16-Promise.resolve({
13+Promise.resolve(
14+new Response(
15+JSON.stringify({
1716candidates: [
1817{
1918content: { parts: [{ text: "Grounded answer" }] },
@@ -23,7 +22,8 @@ function installGeminiFetch() {
2322},
2423],
2524}),
26-} as Response),
25+),
26+),
2727);
2828vi.stubGlobal("fetch", withFetchPreconnect(mockFetch));
2929return mockFetch;
@@ -66,6 +66,7 @@ function getGeminiFetchUrl(mockFetch: ReturnType<typeof installGeminiFetch>): st
6666}
67676868function parseGeminiFetchBody(mockFetch: ReturnType<typeof installGeminiFetch>): {
69+contents?: Array<{ parts?: Array<{ text?: string }> }>;
6970tools?: Array<{ google_search?: { timeRangeFilter?: unknown } }>;
7071} {
7172const [, init] = requireFirstGeminiFetchCall(mockFetch);
@@ -74,6 +75,7 @@ function parseGeminiFetchBody(mockFetch: ReturnType<typeof installGeminiFetch>):
7475throw new Error("Expected Gemini fetch body string");
7576}
7677return JSON.parse(body) as {
78+contents?: Array<{ parts?: Array<{ text?: string }> }>;
7779tools?: Array<{ google_search?: { timeRangeFilter?: unknown } }>;
7880};
7981}
@@ -477,10 +479,37 @@ describe("google web search provider", () => {
477479);
478480});
479481480-it("passes freshness to Gemini Google Search grounding as a time range", async () => {
482+it("uses a soft recency hint for Gemini day freshness shortcuts instead of a 24-hour range", async () => {
483+const mockFetch = installGeminiFetch();
484+const provider = createGeminiWebSearchProvider();
485+const tool = provider.createTool({
486+config: {
487+plugins: {
488+entries: {
489+google: {
490+config: {
491+webSearch: {
492+apiKey: "AIza-plugin-test",
493+},
494+},
495+},
496+},
497+},
498+},
499+searchConfig: { provider: "gemini" },
500+});
501+502+await tool?.execute({ query: "latest ai news timestamp precision", freshness: "pd" });
503+504+const body = parseGeminiFetchBody(mockFetch);
505+expect(body.tools?.[0]?.google_search?.timeRangeFilter).toBeUndefined();
506+expect(body.contents?.[0]?.parts?.[0]?.text).toContain(
507+"Prioritize web sources published in the last 24 hours.",
508+);
509+});
510+511+it("preserves hard Gemini time ranges for wider freshness values", async () => {
481512vi.useFakeTimers({ toFake: ["Date"] });
482-// Use a wall-clock-realistic moment with non-zero milliseconds; the helper
483-// must strip them to avoid Gemini's "Granularity of nano is not supported".
484513vi.setSystemTime(new Date("2026-04-15T12:00:00.123Z"));
485514const mockFetch = installGeminiFetch();
486515const provider = createGeminiWebSearchProvider();
@@ -504,13 +533,68 @@ describe("google web search provider", () => {
504533await tool?.execute({ query: "latest ai news timestamp precision", freshness: "week" });
505534506535const body = parseGeminiFetchBody(mockFetch);
536+expect(body.contents?.[0]?.parts?.[0]?.text).toBe("latest ai news timestamp precision");
507537expect(body.tools?.[0]?.google_search?.timeRangeFilter).toEqual({
508538startTime: "2026-04-08T12:00:00Z",
509539endTime: "2026-04-15T12:00:00Z",
510540});
511541});
512542513-it("strips sub-second precision from freshness timestamps so Gemini accepts them", async () => {
543+it("partitions Gemini cache entries for soft day freshness, hard week freshness, and no freshness", async () => {
544+vi.useFakeTimers({ toFake: ["Date"] });
545+vi.setSystemTime(new Date("2026-04-15T12:00:00.123Z"));
546+const mockFetch = installGeminiFetch();
547+const provider = createGeminiWebSearchProvider();
548+const tool = provider.createTool({
549+config: {
550+plugins: {
551+entries: {
552+google: {
553+config: {
554+webSearch: {
555+apiKey: "AIza-plugin-test",
556+},
557+},
558+},
559+},
560+},
561+},
562+searchConfig: { provider: "gemini" },
563+});
564+565+await tool?.execute({ query: "same query cache partition", freshness: "day" });
566+await tool?.execute({ query: "same query cache partition", freshness: "week" });
567+await tool?.execute({ query: "same query cache partition" });
568+569+const postCalls = mockFetch.mock.calls.filter(([, init]) => typeof init?.body === "string");
570+expect(postCalls).toHaveLength(3);
571+const parsePostedBody = (call: (typeof postCalls)[number] | undefined) => {
572+const body = call?.[1]?.body;
573+if (typeof body !== "string") {
574+throw new Error("Expected Gemini fetch body to be a string");
575+}
576+return JSON.parse(body) as {
577+contents?: Array<{ parts?: Array<{ text?: string }> }>;
578+tools?: Array<{ google_search?: { timeRangeFilter?: unknown } }>;
579+};
580+};
581+const firstBody = parsePostedBody(postCalls[0]);
582+const secondBody = parsePostedBody(postCalls[1]);
583+const thirdBody = parsePostedBody(postCalls[2]);
584+expect(firstBody.tools?.[0]?.google_search?.timeRangeFilter).toBeUndefined();
585+expect(firstBody.contents?.[0]?.parts?.[0]?.text).toContain(
586+"Prioritize web sources published in the last 24 hours.",
587+);
588+expect(secondBody.tools?.[0]?.google_search?.timeRangeFilter).toEqual({
589+startTime: "2026-04-08T12:00:00Z",
590+endTime: "2026-04-15T12:00:00Z",
591+});
592+expect(secondBody.contents?.[0]?.parts?.[0]?.text).toBe("same query cache partition");
593+expect(thirdBody.tools?.[0]?.google_search?.timeRangeFilter).toBeUndefined();
594+expect(thirdBody.contents?.[0]?.parts?.[0]?.text).toBe("same query cache partition");
595+});
596+597+it("strips sub-second precision from date-range timestamps so Gemini accepts them", async () => {
514598vi.useFakeTimers({ toFake: ["Date"] });
515599// "now" with non-zero milliseconds. Without stripping, toISOString() emits
516600// "2026-04-15T12:00:00.123Z", which Gemini's google_search.time_range_filter
@@ -535,7 +619,7 @@ describe("google web search provider", () => {
535619searchConfig: { provider: "gemini" },
536620});
537621538-await tool?.execute({ query: "latest ai news", freshness: "week" });
622+await tool?.execute({ query: "latest ai news", date_after: "2026-04-01" });
539623540624const body = parseGeminiFetchBody(mockFetch);
541625const filter = body.tools?.[0]?.google_search?.timeRangeFilter as
@@ -544,7 +628,7 @@ describe("google web search provider", () => {
544628expect(filter?.startTime).not.toMatch(/\.\d+Z$/);
545629expect(filter?.endTime).not.toMatch(/\.\d+Z$/);
546630expect(filter).toEqual({
547-startTime: "2026-04-08T12:00:00Z",
631+startTime: "2026-04-01T00:00:00Z",
548632endTime: "2026-04-15T12:00:00Z",
549633});
550634});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。