fix(moonshot): forward abort signal through Kimi web search provider (#105125)

This commit is contained in:
wangmiao0668000666
2026-08-02 04:01:40 +08:00
committed by GitHub
parent 81e392b7ff
commit ebb301c32d
3 changed files with 45 additions and 22 deletions

View File

@@ -216,6 +216,7 @@ async function runKimiSearch(params: {
baseUrl: string;
model: string;
timeoutSeconds: number;
signal?: AbortSignal;
}): Promise<KimiSearchResult> {
const endpoint = `${params.baseUrl.trim().replace(/\/$/, "")}/chat/completions`;
const messages: Array<Record<string, unknown>> = [{ role: "user", content: params.query }];
@@ -227,6 +228,7 @@ async function runKimiSearch(params: {
{
url: endpoint,
timeoutSeconds: params.timeoutSeconds,
signal: params.signal,
init: {
method: "POST",
headers: {
@@ -341,6 +343,7 @@ async function runKimiSearch(params: {
export async function executeKimiWebSearchProviderTool(
ctx: { config?: OpenClawConfig; searchConfig?: SearchConfigRecord },
args: Record<string, unknown>,
opts?: { signal?: AbortSignal },
): Promise<Record<string, unknown>> {
const searchConfig = mergeScopedSearchConfig(
ctx.searchConfig,
@@ -392,6 +395,7 @@ export async function executeKimiWebSearchProviderTool(
baseUrl,
model,
timeoutSeconds: resolveSearchTimeoutSeconds(searchConfig),
signal: opts?.signal,
});
if (!result.grounded) {
return {

View File

@@ -1,31 +1,12 @@
// Moonshot tests cover kimi web search provider plugin behavior.
import type { OpenClawConfig } from "openclaw/plugin-sdk/provider-onboard";
import { withEnvAsync } from "openclaw/plugin-sdk/test-env";
import { withEnv, withEnvAsync } from "openclaw/plugin-sdk/test-env";
import { afterEach, describe, expect, it, vi } from "vitest";
import { testing } from "../test-api.js";
import { createKimiWebSearchProvider } from "./kimi-web-search-provider.js";
const kimiApiKeyEnv = ["KIMI_API", "KEY"].join("_");
function withEnv(overrides: Record<string, string>, run: () => void): void {
const previous = new Map<string, string | undefined>();
for (const [key, value] of Object.entries(overrides)) {
previous.set(key, process.env[key]);
process.env[key] = value;
}
try {
run();
} finally {
for (const [key, value] of previous) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
}
}
function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
status: 200,
@@ -286,6 +267,42 @@ describe("kimi web search provider", () => {
).toBeUndefined();
});
it("forwards the execution abort signal to an in-flight Kimi search", async () => {
const fetchMock = vi.fn(
(_url: string, init?: RequestInit) =>
new Promise<Response>((_resolve, reject) => {
init?.signal?.addEventListener(
"abort",
() => reject(new Error(String(init.signal?.reason ?? "Aborted"))),
{
once: true,
},
);
}),
);
vi.stubGlobal("fetch", fetchMock);
await withEnvAsync({ KIMI_API_KEY: "kimi-test-key" }, async () => {
const controller = new AbortController();
const tool = createKimiWebSearchProvider().createTool({ config: {}, searchConfig: {} });
if (!tool) {
throw new Error("Expected tool definition");
}
const search = tool.execute(
{ query: "unique Kimi abort regression" },
{
signal: controller.signal,
},
);
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledOnce());
controller.abort(new Error("Kimi search cancelled"));
await expect(search).rejects.toThrow("Kimi search cancelled");
expect(fetchMock.mock.calls[0]?.[1]?.signal?.aborted).toBe(true);
});
});
it("uses config apiKey when provided", () => {
expect(testing.resolveKimiApiKey({ apiKey: "kimi-test-key" })).toBe("kimi-test-key");
});

View File

@@ -60,9 +60,11 @@ export function createKimiWebSearchProvider(): WebSearchProviderPlugin {
description:
"Search the web using Kimi by Moonshot. Returns AI-synthesized answers with citations from native $web_search.",
parameters: KimiSearchSchema,
execute: async (args) => {
execute: async (args, context) => {
const { executeKimiWebSearchProviderTool } = await loadKimiWebSearchProviderRuntime();
return await executeKimiWebSearchProviderTool(ctx, args);
return await executeKimiWebSearchProviderTool(ctx, args, {
signal: context?.signal,
});
},
}),
};