From a2e0a094c13a831abde286f16cda139b261da9c4 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 4 Apr 2026 23:20:20 +0900 Subject: [PATCH] test(providers): cover stream family plugin hooks --- extensions/google/index.test.ts | 53 ++++++++++++++++++++ extensions/kilocode/index.test.ts | 81 +++++++++++++++++++++++++++++++ extensions/moonshot/index.test.ts | 38 +++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 extensions/kilocode/index.test.ts diff --git a/extensions/google/index.test.ts b/extensions/google/index.test.ts index 5f28be487ae..24c3ba50894 100644 --- a/extensions/google/index.test.ts +++ b/extensions/google/index.test.ts @@ -2,6 +2,8 @@ import type { ProviderReplaySessionEntry, ProviderSanitizeReplayHistoryContext, } from "openclaw/plugin-sdk/plugin-entry"; +import type { StreamFn } from "@mariozechner/pi-agent-core"; +import type { Context, Model } from "@mariozechner/pi-ai"; import { describe, expect, it } from "vitest"; import { registerProviderPlugin, @@ -127,4 +129,55 @@ describe("google provider plugin hooks", () => { } as never), ).toEqual([]); }); + + it("wires google-thinking stream hooks for direct and Gemini CLI providers", async () => { + const { providers } = await registerProviderPlugin({ + plugin: googlePlugin, + id: "google", + name: "Google Provider", + }); + const googleProvider = requireRegisteredProvider(providers, "google"); + const cliProvider = requireRegisteredProvider(providers, "google-gemini-cli"); + let capturedPayload: Record | undefined; + + const baseStreamFn: StreamFn = (model, _context, options) => { + const payload = { config: { thinkingConfig: { thinkingBudget: -1 } } } as Record< + string, + unknown + >; + options?.onPayload?.(payload as never, model as never); + capturedPayload = payload; + return {} as never; + }; + + const runCase = (provider: typeof googleProvider, providerId: string) => { + const wrapped = provider.wrapStreamFn?.({ + provider: providerId, + modelId: "gemini-3.1-pro-preview", + thinkingLevel: "high", + streamFn: baseStreamFn, + } as never); + + void wrapped?.( + { + api: "google-generative-ai", + provider: providerId, + id: "gemini-3.1-pro-preview", + } as Model<"google-generative-ai">, + { messages: [] } as Context, + {}, + ); + + expect(capturedPayload).toMatchObject({ + config: { thinkingConfig: { thinkingLevel: "HIGH" } }, + }); + const thinkingConfig = ( + (capturedPayload as Record).config as Record + ).thinkingConfig as Record; + expect(thinkingConfig).not.toHaveProperty("thinkingBudget"); + }; + + runCase(googleProvider, "google"); + runCase(cliProvider, "google-gemini-cli"); + }); }); diff --git a/extensions/kilocode/index.test.ts b/extensions/kilocode/index.test.ts new file mode 100644 index 00000000000..e30a8f6a3c8 --- /dev/null +++ b/extensions/kilocode/index.test.ts @@ -0,0 +1,81 @@ +import type { StreamFn } from "@mariozechner/pi-agent-core"; +import type { Context, Model } from "@mariozechner/pi-ai"; +import { describe, expect, it } from "vitest"; +import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js"; +import plugin from "./index.js"; + +describe("kilocode provider plugin", () => { + it("owns passthrough-gemini replay policy for Gemini-backed models", async () => { + const provider = await registerSingleProviderPlugin(plugin); + + expect( + provider.buildReplayPolicy?.({ + provider: "kilocode", + modelApi: "openai-completions", + modelId: "gemini-2.5-pro", + } as never), + ).toMatchObject({ + applyAssistantFirstOrderingFix: false, + validateGeminiTurns: false, + validateAnthropicTurns: false, + sanitizeThoughtSignatures: { + allowBase64Only: true, + includeCamelCase: true, + }, + }); + }); + + it("wires kilocode-thinking stream hooks", async () => { + const provider = await registerSingleProviderPlugin(plugin); + let capturedPayload: Record | undefined; + const baseStreamFn: StreamFn = (model, _context, options) => { + const payload = { config: { thinkingConfig: { thinkingBudget: -1 } } } as Record< + string, + unknown + >; + options?.onPayload?.(payload as never, model as never); + capturedPayload = payload; + return {} as never; + }; + + const wrappedReasoning = provider.wrapStreamFn?.({ + provider: "kilocode", + modelId: "openai/gpt-5.4", + thinkingLevel: "high", + streamFn: baseStreamFn, + } as never); + + void wrappedReasoning?.( + { + api: "openai-completions", + provider: "kilocode", + id: "openai/gpt-5.4", + } as Model<"openai-completions">, + { messages: [] } as Context, + {}, + ); + + expect(capturedPayload).toMatchObject({ + reasoning: { effort: "high" }, + }); + + const wrappedAuto = provider.wrapStreamFn?.({ + provider: "kilocode", + modelId: "kilo/auto", + thinkingLevel: "high", + streamFn: baseStreamFn, + } as never); + + void wrappedAuto?.( + { + api: "openai-completions", + provider: "kilocode", + id: "kilo/auto", + } as Model<"openai-completions">, + { messages: [] } as Context, + {}, + ); + + expect(capturedPayload).not.toHaveProperty("reasoning"); + }); +}); diff --git a/extensions/moonshot/index.test.ts b/extensions/moonshot/index.test.ts index 2a244d0c9fd..21aaebc8768 100644 --- a/extensions/moonshot/index.test.ts +++ b/extensions/moonshot/index.test.ts @@ -1,3 +1,5 @@ +import type { StreamFn } from "@mariozechner/pi-agent-core"; +import type { Context, Model } from "@mariozechner/pi-ai"; import { describe, expect, it } from "vitest"; import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js"; import plugin from "./index.js"; @@ -20,4 +22,40 @@ describe("moonshot provider plugin", () => { validateAnthropicTurns: true, }); }); + + it("wires moonshot-thinking stream hooks", async () => { + const provider = await registerSingleProviderPlugin(plugin); + let capturedPayload: Record | undefined; + const baseStreamFn: StreamFn = (model, _context, options) => { + const payload = { config: { thinkingConfig: { thinkingBudget: -1 } } } as Record< + string, + unknown + >; + options?.onPayload?.(payload as never, model as never); + capturedPayload = payload; + return {} as never; + }; + + const wrapped = provider.wrapStreamFn?.({ + provider: "moonshot", + modelId: "kimi-k2.5", + thinkingLevel: "off", + streamFn: baseStreamFn, + } as never); + + void wrapped?.( + { + api: "openai-completions", + provider: "moonshot", + id: "kimi-k2.5", + } as Model<"openai-completions">, + { messages: [] } as Context, + {}, + ); + + expect(capturedPayload).toMatchObject({ + config: { thinkingConfig: { thinkingBudget: -1 } }, + thinking: { type: "disabled" }, + }); + }); });