import assert from "node:assert/strict"; import test from "node:test"; import { SearchClient, type SearchResponse } from "coze-coding-dev-sdk"; import { cozeWebSearchTool } from "../src/tools/web-search/index.js"; function resultText(result: { content: { type: string; text?: string }[] }): string { return result.content[0].text ?? ""; } function makeWebSearchResponse(overrides: Partial = {}): SearchResponse { return { web_items: [ { id: "1", sort_id: 1, title: "Result A", site_name: "example.com", url: "https://example.com/a", snippet: "Snippet A", auth_info_des: "", auth_info_level: 0, }, { id: "2", sort_id: 2, title: "Result B", url: "https://example.com/b", snippet: "Snippet B", publish_time: "2025-01-01", auth_info_des: "", auth_info_level: 0, }, ], image_items: [], ...overrides, }; } function makeImageSearchResponse(): SearchResponse { return { web_items: [], image_items: [ { id: "img-1", sort_id: 1, title: "Cat Photo", url: "https://example.com/cat", site_name: "photos.com", image: { url: "https://cdn.com/cat.jpg", width: 800, height: 600, shape: "rect" }, }, { id: "img-2", sort_id: 2, url: "https://example.com/dog", image: { url: "https://cdn.com/dog.jpg", width: 1024, height: 768, shape: "rect" }, }, ], }; } test("web-search: basic web search returns formatted text", async (t) => { const resp = makeWebSearchResponse(); t.mock.method(SearchClient.prototype, "webSearch", async () => resp); const result = await cozeWebSearchTool.execute("call-1", { query: "test" }, undefined as any, undefined as any, undefined as any); const text = resultText(result); assert.ok(text.includes("Coze web search: test")); assert.ok(text.includes("Results (2)")); assert.ok(text.includes("1. Result A")); assert.ok(text.includes("URL: https://example.com/a")); assert.ok(text.includes("Source: example.com")); assert.ok(text.includes("Snippet A")); assert.ok(text.includes("2. Result B")); assert.ok(text.includes("Published: 2025-01-01")); }); test("web-search: includes summary when present", async (t) => { const resp = makeWebSearchResponse({ summary: "This is the summary" }); t.mock.method(SearchClient.prototype, "webSearch", async () => resp); const result = await cozeWebSearchTool.execute("call-2", { query: "test", needSummary: true }, undefined as any, undefined as any, undefined as any); const text = resultText(result); assert.ok(text.includes("Summary: This is the summary")); }); test("web-search: includes content when needContent is true", async (t) => { const resp = makeWebSearchResponse(); resp.web_items[0].content = "Full page content here"; t.mock.method(SearchClient.prototype, "advancedSearch", async () => resp); const result = await cozeWebSearchTool.execute("call-3", { query: "test", needContent: true }, undefined as any, undefined as any, undefined as any); const text = resultText(result); assert.ok(text.includes("Content:")); assert.ok(text.includes("Full page content here")); }); test("web-search: image search maps image items", async (t) => { const resp = makeImageSearchResponse(); t.mock.method(SearchClient.prototype, "imageSearch", async () => resp); const result = await cozeWebSearchTool.execute("call-4", { query: "cats", type: "image" }, undefined as any, undefined as any, undefined as any); const text = resultText(result); assert.ok(text.includes("1. Cat Photo")); assert.ok(text.includes("Image: https://cdn.com/cat.jpg")); assert.ok(text.includes("2. Untitled")); assert.ok(text.includes("Image: https://cdn.com/dog.jpg")); assert.equal((result as any).details.type, "image"); }); test("web-search: uses advancedSearch when filters are present", async (t) => { const resp = makeWebSearchResponse(); const calls: string[] = []; t.mock.method(SearchClient.prototype, "advancedSearch", async () => { calls.push("advancedSearch"); return resp; }); t.mock.method(SearchClient.prototype, "webSearch", async () => { calls.push("webSearch"); return resp; }); await cozeWebSearchTool.execute("call-5", { query: "test", timeRange: "1d" }, undefined as any, undefined as any, undefined as any); assert.deepEqual(calls, ["advancedSearch"]); }); test("web-search: uses webSearch when no filters present", async (t) => { const resp = makeWebSearchResponse(); const calls: string[] = []; t.mock.method(SearchClient.prototype, "advancedSearch", async () => { calls.push("advancedSearch"); return resp; }); t.mock.method(SearchClient.prototype, "webSearch", async () => { calls.push("webSearch"); return resp; }); await cozeWebSearchTool.execute("call-6", { query: "test" }, undefined as any, undefined as any, undefined as any); assert.deepEqual(calls, ["webSearch"]); }); test("web-search: uses advancedSearch when sites filter is set", async (t) => { const resp = makeWebSearchResponse(); const calls: string[] = []; t.mock.method(SearchClient.prototype, "advancedSearch", async () => { calls.push("advancedSearch"); return resp; }); t.mock.method(SearchClient.prototype, "webSearch", async () => { calls.push("webSearch"); return resp; }); await cozeWebSearchTool.execute("call-7", { query: "test", sites: "example.com" }, undefined as any, undefined as any, undefined as any); assert.deepEqual(calls, ["advancedSearch"]); }); test("web-search: returns error content on search failure", async (t) => { t.mock.method(SearchClient.prototype, "webSearch", async () => { throw new Error("API timeout"); }); const result = await cozeWebSearchTool.execute("call-8", { query: "test" }, undefined as any, undefined as any, undefined as any); assert.ok(resultText(result).includes("Error: API timeout")); assert.equal((result as any).details.error, true); }); test("web-search: defaults count to 10 and type to web", async (t) => { const captured: Array<{ query: string; count: number; needSummary?: boolean }> = []; t.mock.method(SearchClient.prototype, "webSearch", async (query: string, count: number, needSummary?: boolean) => { captured.push({ query, count, needSummary }); return makeWebSearchResponse(); }); await cozeWebSearchTool.execute("call-9", { query: "hello" }, undefined as any, undefined as any, undefined as any); assert.equal(captured[0].query, "hello"); assert.equal(captured[0].count, 10); assert.equal(captured[0].needSummary, undefined); }); test("web-search: details contain correct metadata", async (t) => { const resp = makeWebSearchResponse({ summary: "Sum" }); t.mock.method(SearchClient.prototype, "webSearch", async () => resp); const result = await cozeWebSearchTool.execute("call-10", { query: "meta test" }, undefined as any, undefined as any, undefined as any); assert.equal((result as any).details.query, "meta test"); assert.equal((result as any).details.type, "web"); assert.equal((result as any).details.summary, "Sum"); assert.equal((result as any).details.count, 2); }); test("web-search: empty results produces correct output", async (t) => { const resp: SearchResponse = { web_items: [], image_items: [] }; t.mock.method(SearchClient.prototype, "webSearch", async () => resp); const result = await cozeWebSearchTool.execute("call-11", { query: "nothing" }, undefined as any, undefined as any, undefined as any); const text = resultText(result); assert.ok(text.includes("Results (0)")); assert.equal((result as any).details.count, 0); });