summaryrefslogtreecommitdiffhomepage
path: root/packages/tool-web-search/src/validate.test.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-21 13:11:29 +0900
committerAdam Malczewski <[email protected]>2026-06-21 13:11:29 +0900
commit8a4a624d16422467a8e85434c674bb591877e8ea (patch)
tree54052da00bbc580742913e5c031b7cc1b160db19 /packages/tool-web-search/src/validate.test.ts
parentd23de3254374d4d63c8e15c6ab9311c3c6f4da5b (diff)
downloaddispatch-8a4a624d16422467a8e85434c674bb591877e8ea.tar.gz
dispatch-8a4a624d16422467a8e85434c674bb591877e8ea.zip
feat(tool-web-search): Firecrawl-backed web search tool
New standard tool extension with one tool web_search supporting 4 modes (search, scrape, crawl, map) against a self-hosted Firecrawl instance. Pure core: validateArgs (discriminated union by mode) + format* functions + truncateOutput. Injected edge: FirecrawlClient (injectable fetchFn/sleep/now, AbortSignal.any for per-request timeout + caller cancellation). concurrencySafe true, capabilities network. 38 tests, zero vi.mock. Live-verified: umans-glm-5.2 called web_search → real Firecrawl results (also the first live Umans API call).
Diffstat (limited to 'packages/tool-web-search/src/validate.test.ts')
-rw-r--r--packages/tool-web-search/src/validate.test.ts92
1 files changed, 92 insertions, 0 deletions
diff --git a/packages/tool-web-search/src/validate.test.ts b/packages/tool-web-search/src/validate.test.ts
new file mode 100644
index 0000000..30ae26c
--- /dev/null
+++ b/packages/tool-web-search/src/validate.test.ts
@@ -0,0 +1,92 @@
+import { describe, expect, it } from "vitest";
+import {
+ type CrawlArgs,
+ type MapArgs,
+ type ScrapeArgs,
+ type SearchArgs,
+ validateArgs,
+} from "./validate.js";
+
+describe("validateArgs", () => {
+ it("mode defaults to search when query present", () => {
+ const result = validateArgs({ query: "hello" });
+ expect("error" in result).toBe(false);
+ if ("error" in result) return;
+ expect(result.mode).toBe("search");
+ expect((result as SearchArgs).query).toBe("hello");
+ });
+
+ it("mode defaults to scrape when url present (no query)", () => {
+ const result = validateArgs({ url: "http://example.com" });
+ expect("error" in result).toBe(false);
+ if ("error" in result) return;
+ expect(result.mode).toBe("scrape");
+ expect((result as ScrapeArgs).url).toBe("http://example.com");
+ });
+
+ it("explicit mode overrides defaults", () => {
+ const result = validateArgs({ query: "hello", url: "http://x", mode: "map" });
+ expect("error" in result).toBe(false);
+ if ("error" in result) return;
+ expect(result.mode).toBe("map");
+ expect((result as MapArgs).url).toBe("http://x");
+ });
+
+ it("search mode requires query", () => {
+ const result = validateArgs({ mode: "search" });
+ expect(result).toHaveProperty("error");
+ });
+
+ it("scrape/crawl/map modes require url", () => {
+ expect(validateArgs({ mode: "scrape" })).toHaveProperty("error");
+ expect(validateArgs({ mode: "crawl" })).toHaveProperty("error");
+ expect(validateArgs({ mode: "map" })).toHaveProperty("error");
+ });
+
+ it("limit clamped to max 10", () => {
+ const result = validateArgs({ query: "hello", limit: 50 });
+ expect("error" in result).toBe(false);
+ if ("error" in result) return;
+ expect((result as SearchArgs).limit).toBe(10);
+ });
+
+ it("limit defaults to 7 (search) / 3 (crawl)", () => {
+ const search = validateArgs({ query: "hello" });
+ expect("error" in search).toBe(false);
+ if ("error" in search) return;
+ expect((search as SearchArgs).limit).toBe(7);
+
+ const crawl = validateArgs({ url: "http://x", mode: "crawl" });
+ expect("error" in crawl).toBe(false);
+ if ("error" in crawl) return;
+ expect((crawl as CrawlArgs).limit).toBe(3);
+ });
+
+ it("format defaults to markdown", () => {
+ const result = validateArgs({ query: "hello" });
+ expect("error" in result).toBe(false);
+ if ("error" in result) return;
+ expect(result.format).toBe("markdown");
+ });
+
+ it("rejects invalid mode", () => {
+ const result = validateArgs({ mode: "invalid" });
+ expect(result).toHaveProperty("error");
+ if (!("error" in result)) return;
+ expect(result.error).toContain("Invalid mode");
+ });
+
+ it("rejects invalid format", () => {
+ const result = validateArgs({ url: "http://x", format: "pdf" });
+ expect(result).toHaveProperty("error");
+ if (!("error" in result)) return;
+ expect(result.error).toContain("Invalid format");
+ });
+
+ it("returns error for null/non-object args", () => {
+ expect(validateArgs(null)).toHaveProperty("error");
+ expect(validateArgs(undefined)).toHaveProperty("error");
+ expect(validateArgs("string")).toHaveProperty("error");
+ expect(validateArgs(42)).toHaveProperty("error");
+ });
+});