tools: add Serper Google Search API as web_search provider option

This commit is contained in:
Roger M 2026-01-26 15:26:22 +00:00 committed by rogerserper
parent d52835e1a0
commit 8041703a29
3 changed files with 103 additions and 1 deletions

View File

@ -307,3 +307,87 @@ describe("web_search perplexity baseUrl defaults", () => {
expect(mockFetch.mock.calls[0]?.[0]).toBe("https://openrouter.ai/api/v1/chat/completions");
});
});
describe("web_search serper provider", () => {
const priorFetch = global.fetch;
afterEach(() => {
vi.unstubAllEnvs();
// @ts-expect-error global fetch cleanup
global.fetch = priorFetch;
});
it("works with Serper provider and config API key", async () => {
const mockFetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
organic: [
{ title: "Test Result", link: "https://example.com", snippet: "Test snippet" },
],
}),
} as Response),
);
// @ts-expect-error mock fetch
global.fetch = mockFetch;
const tool = createWebSearchTool({
config: {
tools: {
web: {
search: {
provider: "serper",
apiKey: "serper-test-key",
},
},
},
},
sandboxed: true,
});
const result = await tool?.execute?.(1, { query: "test serper" });
expect(mockFetch).toHaveBeenCalled();
expect(mockFetch.mock.calls[0]?.[0]).toBe("https://google.serper.dev/search");
expect(mockFetch.mock.calls[0]?.[1]?.method).toBe("POST");
expect(mockFetch.mock.calls[0]?.[1]?.headers?.["X-API-KEY"]).toBe("serper-test-key");
expect(result?.details).toMatchObject({
provider: "serper",
query: "test serper",
});
});
it("works with Serper provider and SERPER_API_KEY env var", async () => {
vi.stubEnv("SERPER_API_KEY", "serper-env-key");
const mockFetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
organic: [
{ title: "Test Result", link: "https://example.com", snippet: "Test snippet" },
],
}),
} as Response),
);
// @ts-expect-error mock fetch
global.fetch = mockFetch;
const tool = createWebSearchTool({
config: {
tools: {
web: {
search: {
provider: "serper",
},
},
},
},
sandboxed: true,
});
await tool?.execute?.(1, { query: "test serper env" });
expect(mockFetch).toHaveBeenCalled();
expect(mockFetch.mock.calls[0]?.[1]?.headers?.["X-API-KEY"]).toBe("serper-env-key");
});
});

View File

@ -22,4 +22,20 @@ describe("web search provider config", () => {
expect(res.ok).toBe(true);
});
it("accepts serper provider and config", () => {
const res = validateConfigObject({
tools: {
web: {
search: {
enabled: true,
provider: "serper",
apiKey: "test-key",
},
},
},
});
expect(res.ok).toBe(true);
});
});

View File

@ -168,7 +168,9 @@ export const ToolPolicySchema = ToolPolicyBaseSchema.superRefine((value, ctx) =>
export const ToolsWebSearchSchema = z
.object({
enabled: z.boolean().optional(),
provider: z.union([z.literal("brave"), z.literal("perplexity")]).optional(),
provider: z
.union([z.literal("brave"), z.literal("perplexity"), z.literal("serper")])
.optional(),
apiKey: z.string().optional(),
maxResults: z.number().int().positive().optional(),
timeoutSeconds: z.number().int().positive().optional(),