mirror of https://github.com/openclaw/openclaw.git
feat: add support for extra headers in Tavily API requests (#55335)
* feat: add support for extra headers in Tavily API requests * test(tavily-client): add unit tests for X-Client-Source header in API calls * fix(tavily): add client source attribution (#55335) (thanks @lakshyaag-tavily) --------- Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
This commit is contained in:
parent
6777764a6b
commit
4dfd2cd60c
|
|
@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Docs: add `pnpm docs:check-links:anchors` for Mintlify anchor validation while keeping `scripts/docs-link-audit.mjs` as the stable link-audit entrypoint. (#55912) Thanks @velvet-shark.
|
||||
- Plugins/hooks: add async `requireApproval` to `before_tool_call` hooks, letting plugins pause tool execution and prompt the user for approval via the exec approval overlay, Telegram buttons, Discord interactions, or the `/approve` command on any channel. The `/approve` command now handles both exec and plugin approvals with automatic fallback. (#55339) Thanks @vaclavbelak and @joshavant.
|
||||
- ACP/channels: add current-conversation ACP binds for Discord, BlueBubbles, and iMessage so `/acp spawn codex --bind here` can turn the current chat into a Codex-backed workspace without creating a child thread, and document the distinction between chat surface, ACP session, and runtime workspace.
|
||||
- Tavily: mark outbound API requests with `X-Client-Source: openclaw` so Tavily can attribute OpenClaw-originated traffic. (#55335) Thanks @lakshyaag-tavily.
|
||||
|
||||
### Fixes
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// Capture every call to postTrustedWebToolsJson so we can assert on extraHeaders.
|
||||
const postTrustedWebToolsJson = vi.fn();
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/provider-web-search", () => ({
|
||||
DEFAULT_CACHE_TTL_MINUTES: 5,
|
||||
normalizeCacheKey: (k: string) => k,
|
||||
postTrustedWebToolsJson,
|
||||
readCache: () => undefined,
|
||||
resolveCacheTtlMs: () => 300_000,
|
||||
writeCache: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/security-runtime", () => ({
|
||||
wrapExternalContent: (v: string) => v,
|
||||
wrapWebContent: (v: string) => v,
|
||||
}));
|
||||
|
||||
vi.mock("./config.js", () => ({
|
||||
DEFAULT_TAVILY_BASE_URL: "https://api.tavily.com",
|
||||
resolveTavilyApiKey: () => "test-key",
|
||||
resolveTavilyBaseUrl: () => "https://api.tavily.com",
|
||||
resolveTavilySearchTimeoutSeconds: () => 30,
|
||||
resolveTavilyExtractTimeoutSeconds: () => 60,
|
||||
}));
|
||||
|
||||
describe("tavily client X-Client-Source header", () => {
|
||||
let runTavilySearch: typeof import("./tavily-client.js").runTavilySearch;
|
||||
let runTavilyExtract: typeof import("./tavily-client.js").runTavilyExtract;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
postTrustedWebToolsJson.mockReset();
|
||||
postTrustedWebToolsJson.mockImplementation(
|
||||
async (_params: unknown, parse: (r: Response) => Promise<unknown>) =>
|
||||
parse(Response.json({ results: [] })),
|
||||
);
|
||||
|
||||
({ runTavilySearch, runTavilyExtract } = await import("./tavily-client.js"));
|
||||
});
|
||||
|
||||
it("runTavilySearch sends X-Client-Source: openclaw", async () => {
|
||||
await runTavilySearch({ query: "test query" });
|
||||
|
||||
expect(postTrustedWebToolsJson).toHaveBeenCalledOnce();
|
||||
const params = postTrustedWebToolsJson.mock.calls[0][0];
|
||||
expect(params.extraHeaders).toEqual({ "X-Client-Source": "openclaw" });
|
||||
});
|
||||
|
||||
it("runTavilyExtract sends X-Client-Source: openclaw", async () => {
|
||||
await runTavilyExtract({ urls: ["https://example.com"] });
|
||||
|
||||
expect(postTrustedWebToolsJson).toHaveBeenCalledOnce();
|
||||
const params = postTrustedWebToolsJson.mock.calls[0][0];
|
||||
expect(params.extraHeaders).toEqual({ "X-Client-Source": "openclaw" });
|
||||
});
|
||||
});
|
||||
|
|
@ -119,6 +119,7 @@ export async function runTavilySearch(
|
|||
apiKey,
|
||||
body,
|
||||
errorLabel: "Tavily Search",
|
||||
extraHeaders: { "X-Client-Source": "openclaw" },
|
||||
},
|
||||
async (response) => (await response.json()) as Record<string, unknown>,
|
||||
);
|
||||
|
|
@ -200,6 +201,7 @@ export async function runTavilyExtract(
|
|||
apiKey,
|
||||
body,
|
||||
errorLabel: "Tavily Extract",
|
||||
extraHeaders: { "X-Client-Source": "openclaw" },
|
||||
},
|
||||
async (response) => (await response.json()) as Record<string, unknown>,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ export async function postTrustedWebToolsJson<T>(
|
|||
body: Record<string, unknown>;
|
||||
errorLabel: string;
|
||||
maxErrorBytes?: number;
|
||||
extraHeaders?: Record<string, string>;
|
||||
},
|
||||
parseResponse: (response: Response) => Promise<T>,
|
||||
): Promise<T> {
|
||||
|
|
@ -110,6 +111,7 @@ export async function postTrustedWebToolsJson<T>(
|
|||
init: {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...params.extraHeaders,
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
|
|
|
|||
Loading…
Reference in New Issue