import { getScopedCredentialValue, resolveWebSearchProviderCredential, } from "openclaw/plugin-sdk/provider-web-search"; import { describe, expect, it } from "vitest"; import { withEnv } from "../../test/helpers/extensions/env.js"; import { __testing } from "./web-search.js"; const { extractXaiWebSearchContent, resolveXaiInlineCitations, resolveXaiWebSearchModel } = __testing; describe("xai web search config resolution", () => { it("uses config apiKey when provided", () => { const searchConfig = { grok: { apiKey: "xai-test-key" } }; // pragma: allowlist secret expect( resolveWebSearchProviderCredential({ credentialValue: getScopedCredentialValue(searchConfig, "grok"), path: "tools.web.search.grok.apiKey", envVars: ["XAI_API_KEY"], }), ).toBe("xai-test-key"); }); it("returns undefined when no apiKey is available", () => { withEnv({ XAI_API_KEY: undefined }, () => { expect( resolveWebSearchProviderCredential({ credentialValue: getScopedCredentialValue({}, "grok"), path: "tools.web.search.grok.apiKey", envVars: ["XAI_API_KEY"], }), ).toBeUndefined(); }); }); it("uses default model when not specified", () => { expect(resolveXaiWebSearchModel({})).toBe("grok-4-1-fast"); expect(resolveXaiWebSearchModel(undefined)).toBe("grok-4-1-fast"); }); it("uses config model when provided", () => { expect(resolveXaiWebSearchModel({ grok: { model: "grok-4-fast-reasoning" } })).toBe( "grok-4-fast-reasoning", ); }); it("normalizes deprecated grok 4.20 beta model ids to GA ids", () => { expect( resolveXaiWebSearchModel({ grok: { model: "grok-4.20-experimental-beta-0304-reasoning" }, }), ).toBe("grok-4.20-reasoning"); expect( resolveXaiWebSearchModel({ grok: { model: "grok-4.20-experimental-beta-0304-non-reasoning" }, }), ).toBe("grok-4.20-non-reasoning"); }); it("defaults inlineCitations to false", () => { expect(resolveXaiInlineCitations({})).toBe(false); expect(resolveXaiInlineCitations(undefined)).toBe(false); }); it("respects inlineCitations config", () => { expect(resolveXaiInlineCitations({ grok: { inlineCitations: true } })).toBe(true); expect(resolveXaiInlineCitations({ grok: { inlineCitations: false } })).toBe(false); }); }); describe("xai web search response parsing", () => { it("extracts content from Responses API message blocks", () => { const result = extractXaiWebSearchContent({ output: [ { type: "message", content: [{ type: "output_text", text: "hello from output" }], }, ], }); expect(result.text).toBe("hello from output"); expect(result.annotationCitations).toEqual([]); }); it("extracts url_citation annotations from content blocks", () => { const result = extractXaiWebSearchContent({ output: [ { type: "message", content: [ { type: "output_text", text: "hello with citations", annotations: [ { type: "url_citation", url: "https://example.com/a" }, { type: "url_citation", url: "https://example.com/b" }, { type: "url_citation", url: "https://example.com/a" }, ], }, ], }, ], }); expect(result.text).toBe("hello with citations"); expect(result.annotationCitations).toEqual(["https://example.com/a", "https://example.com/b"]); }); it("falls back to deprecated output_text", () => { const result = extractXaiWebSearchContent({ output_text: "hello from output_text" }); expect(result.text).toBe("hello from output_text"); expect(result.annotationCitations).toEqual([]); }); it("returns undefined text when no content found", () => { const result = extractXaiWebSearchContent({}); expect(result.text).toBeUndefined(); expect(result.annotationCitations).toEqual([]); }); it("extracts output_text blocks directly in output array", () => { const result = extractXaiWebSearchContent({ output: [ { type: "web_search_call" }, { type: "output_text", text: "direct output text", annotations: [{ type: "url_citation", url: "https://example.com/direct" }], }, ], }); expect(result.text).toBe("direct output text"); expect(result.annotationCitations).toEqual(["https://example.com/direct"]); }); });