* Extensions: fix oxfmt drift on main * Plugins: restore runtime barrel exports on main * Config: restore web search compatibility types * Telegram: align test harness with reply runtime * Plugin SDK: fix channel config accessor generics * CLI: remove redundant search provider casts * Tests: restore main typecheck coverage * Lobster: fix test import formatting * Extensions: route bundled seams through plugin-sdk * Tests: use extension env helper for xai * Image generation: fix main oxfmt drift * Config: restore latest main compatibility checks * Plugin SDK: align guardrail tests with lint * Telegram: type native command skill mock
122 lines
4.1 KiB
TypeScript
122 lines
4.1 KiB
TypeScript
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("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"]);
|
|
});
|
|
});
|