From 9c42e6424d2505f99484b1ac4b69ed2b4e0d7201 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 14 Apr 2026 16:23:29 +0100 Subject: [PATCH] fix(plugins): share tool-stream defaults and align xai sdk imports --- extensions/xai/api.ts | 6 ++--- extensions/xai/index.test.ts | 24 +++++++++++++++++++ extensions/xai/index.ts | 12 ++-------- extensions/xai/onboard.ts | 2 +- extensions/xai/provider-catalog.ts | 2 +- extensions/xai/setup-api.ts | 2 +- extensions/xai/src/code-execution-shared.ts | 2 +- extensions/xai/src/tool-auth-shared.ts | 8 +++---- extensions/xai/src/web-search-shared.ts | 2 +- extensions/xai/src/x-search-config.ts | 2 +- extensions/xai/src/x-search-shared.ts | 2 +- extensions/xai/stream.ts | 4 ++-- extensions/zai/index.test.ts | 24 +++++++++++++++++++ extensions/zai/index.ts | 11 ++------- src/plugin-sdk/provider-stream-shared.test.ts | 19 +++++++++++++++ src/plugin-sdk/provider-stream-shared.ts | 12 ++++++++++ src/plugin-sdk/provider-stream.ts | 1 + 17 files changed, 100 insertions(+), 35 deletions(-) diff --git a/extensions/xai/api.ts b/extensions/xai/api.ts index d32295ada82..721400b3f8a 100644 --- a/extensions/xai/api.ts +++ b/extensions/xai/api.ts @@ -3,11 +3,11 @@ import { normalizeNativeXaiModelId, normalizeProviderId, resolveProviderEndpoint, -} from "@openclaw/plugin-sdk/provider-model-shared"; +} from "openclaw/plugin-sdk/provider-model-shared"; import { applyXaiModelCompat, resolveXaiModelCompatPatch, -} from "@openclaw/plugin-sdk/provider-tools"; +} from "openclaw/plugin-sdk/provider-tools"; import { readStringValue } from "openclaw/plugin-sdk/text-runtime"; export { buildXaiProvider } from "./provider-catalog.js"; @@ -28,7 +28,7 @@ export { HTML_ENTITY_TOOL_CALL_ARGUMENTS_ENCODING, XAI_TOOL_SCHEMA_PROFILE, resolveXaiModelCompatPatch, -} from "@openclaw/plugin-sdk/provider-tools"; +} from "openclaw/plugin-sdk/provider-tools"; function isXaiNativeEndpoint(baseUrl: unknown): boolean { return ( diff --git a/extensions/xai/index.test.ts b/extensions/xai/index.test.ts index c4e61fc112c..6c2f0f12e67 100644 --- a/extensions/xai/index.test.ts +++ b/extensions/xai/index.test.ts @@ -88,4 +88,28 @@ describe("xai provider plugin", () => { (capturedPayload?.tools as Array<{ function?: Record }>)[0]?.function, ).not.toHaveProperty("strict"); }); + + it("defaults tool_stream extra params but preserves explicit values", async () => { + const provider = await registerSingleProviderPlugin(plugin); + + expect( + provider.prepareExtraParams?.({ + provider: "xai", + modelId: "grok-4", + extraParams: { fastMode: true }, + } as never), + ).toEqual({ + fastMode: true, + tool_stream: true, + }); + + const explicit = { fastMode: true, tool_stream: false }; + expect( + provider.prepareExtraParams?.({ + provider: "xai", + modelId: "grok-4", + extraParams: explicit, + } as never), + ).toBe(explicit); + }); }); diff --git a/extensions/xai/index.ts b/extensions/xai/index.ts index c38774147ea..9d1d37f6a12 100644 --- a/extensions/xai/index.ts +++ b/extensions/xai/index.ts @@ -1,6 +1,7 @@ import { Type } from "@sinclair/typebox"; import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry"; import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared"; +import { defaultToolStreamExtraParams } from "openclaw/plugin-sdk/provider-stream-shared"; import { jsonResult, readProviderEnvValue } from "openclaw/plugin-sdk/provider-web-search"; import { applyXaiModelCompat, @@ -160,16 +161,7 @@ export default defineSingleProviderPluginEntry({ buildProvider: buildXaiProvider, }, ...OPENAI_COMPATIBLE_REPLAY_HOOKS, - prepareExtraParams: (ctx) => { - const extraParams = ctx.extraParams; - if (extraParams && extraParams.tool_stream !== undefined) { - return extraParams; - } - return { - ...extraParams, - tool_stream: true, - }; - }, + prepareExtraParams: (ctx) => defaultToolStreamExtraParams(ctx.extraParams), wrapStreamFn: wrapXaiProviderStream, // Provider-specific fallback auth stays owned by the xAI plugin so core // auth/discovery code can consume it generically without parsing xAI's diff --git a/extensions/xai/onboard.ts b/extensions/xai/onboard.ts index 9a9cddd0bfa..bf4b4967fdf 100644 --- a/extensions/xai/onboard.ts +++ b/extensions/xai/onboard.ts @@ -1,7 +1,7 @@ import { createDefaultModelsPresetAppliers, type OpenClawConfig, -} from "@openclaw/plugin-sdk/provider-onboard"; +} from "openclaw/plugin-sdk/provider-onboard"; import { XAI_BASE_URL, XAI_DEFAULT_MODEL_ID } from "./model-definitions.js"; import { buildXaiCatalogModels } from "./model-definitions.js"; diff --git a/extensions/xai/provider-catalog.ts b/extensions/xai/provider-catalog.ts index 9dca2fb1957..f8c248d60de 100644 --- a/extensions/xai/provider-catalog.ts +++ b/extensions/xai/provider-catalog.ts @@ -1,4 +1,4 @@ -import type { ModelProviderConfig } from "@openclaw/plugin-sdk/provider-model-shared"; +import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; import { buildXaiCatalogModels, XAI_BASE_URL } from "./model-definitions.js"; export function buildXaiProvider( diff --git a/extensions/xai/setup-api.ts b/extensions/xai/setup-api.ts index c849382c2f1..a5d5cb9f902 100644 --- a/extensions/xai/setup-api.ts +++ b/extensions/xai/setup-api.ts @@ -1,4 +1,4 @@ -import { definePluginEntry } from "@openclaw/plugin-sdk/plugin-entry"; +import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { isRecord } from "./src/tool-config-shared.js"; export default definePluginEntry({ diff --git a/extensions/xai/src/code-execution-shared.ts b/extensions/xai/src/code-execution-shared.ts index 3c34c269bde..f274348eba5 100644 --- a/extensions/xai/src/code-execution-shared.ts +++ b/extensions/xai/src/code-execution-shared.ts @@ -1,4 +1,4 @@ -import { postTrustedWebToolsJson } from "@openclaw/plugin-sdk/provider-web-search"; +import { postTrustedWebToolsJson } from "openclaw/plugin-sdk/provider-web-search"; import { buildXaiResponsesToolBody, resolveXaiResponseTextAndCitations, diff --git a/extensions/xai/src/tool-auth-shared.ts b/extensions/xai/src/tool-auth-shared.ts index 778d15a4b7d..c82d2da532a 100644 --- a/extensions/xai/src/tool-auth-shared.ts +++ b/extensions/xai/src/tool-auth-shared.ts @@ -1,14 +1,14 @@ -import type { OpenClawConfig } from "@openclaw/plugin-sdk/config-runtime"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { coerceSecretRef, resolveNonEnvSecretRefApiKeyMarker, -} from "@openclaw/plugin-sdk/provider-auth"; +} from "openclaw/plugin-sdk/provider-auth"; import { readProviderEnvValue, readConfiguredSecretString, resolveProviderWebSearchPluginConfig, -} from "@openclaw/plugin-sdk/provider-web-search"; -import { normalizeSecretInputString } from "@openclaw/plugin-sdk/secret-input"; +} from "openclaw/plugin-sdk/provider-web-search"; +import { normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input"; export type XaiFallbackAuth = { apiKey: string; diff --git a/extensions/xai/src/web-search-shared.ts b/extensions/xai/src/web-search-shared.ts index 394ec3a1720..c6b89eab1be 100644 --- a/extensions/xai/src/web-search-shared.ts +++ b/extensions/xai/src/web-search-shared.ts @@ -1,4 +1,4 @@ -import { postTrustedWebToolsJson, wrapWebContent } from "@openclaw/plugin-sdk/provider-web-search"; +import { postTrustedWebToolsJson, wrapWebContent } from "openclaw/plugin-sdk/provider-web-search"; import { normalizeXaiModelId } from "../model-id.js"; import { buildXaiResponsesToolBody, diff --git a/extensions/xai/src/x-search-config.ts b/extensions/xai/src/x-search-config.ts index f8a89e3f813..0595f96602e 100644 --- a/extensions/xai/src/x-search-config.ts +++ b/extensions/xai/src/x-search-config.ts @@ -1,4 +1,4 @@ -import type { OpenClawConfig } from "@openclaw/plugin-sdk/config-runtime"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { isRecord } from "./tool-config-shared.js"; type JsonRecord = Record; diff --git a/extensions/xai/src/x-search-shared.ts b/extensions/xai/src/x-search-shared.ts index c32384aed84..53a41f4ed10 100644 --- a/extensions/xai/src/x-search-shared.ts +++ b/extensions/xai/src/x-search-shared.ts @@ -1,4 +1,4 @@ -import { postTrustedWebToolsJson, wrapWebContent } from "@openclaw/plugin-sdk/provider-web-search"; +import { postTrustedWebToolsJson, wrapWebContent } from "openclaw/plugin-sdk/provider-web-search"; import { buildXaiResponsesToolBody, resolveXaiResponseTextCitationsAndInline, diff --git a/extensions/xai/stream.ts b/extensions/xai/stream.ts index 9af24c9d63a..e3878bce5d8 100644 --- a/extensions/xai/stream.ts +++ b/extensions/xai/stream.ts @@ -1,11 +1,11 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; import { streamSimple } from "@mariozechner/pi-ai"; -import type { ProviderWrapStreamFnContext } from "@openclaw/plugin-sdk/plugin-entry"; +import type { ProviderWrapStreamFnContext } from "openclaw/plugin-sdk/plugin-entry"; import { composeProviderStreamWrappers, createHtmlEntityToolCallArgumentDecodingWrapper, createToolStreamWrapper, -} from "@openclaw/plugin-sdk/provider-stream-shared"; +} from "openclaw/plugin-sdk/provider-stream-shared"; const XAI_FAST_MODEL_IDS = new Map([ ["grok-3", "grok-3-fast"], diff --git a/extensions/zai/index.test.ts b/extensions/zai/index.test.ts index 640bc26b36b..a8e04d9e6b2 100644 --- a/extensions/zai/index.test.ts +++ b/extensions/zai/index.test.ts @@ -215,4 +215,28 @@ describe("zai provider plugin", () => { expect(capturedPayload).not.toHaveProperty("tool_stream"); }); + + it("defaults tool_stream extra params but preserves explicit values", async () => { + const provider = await registerSingleProviderPlugin(plugin); + + expect( + provider.prepareExtraParams?.({ + provider: "zai", + modelId: "glm-4.7", + extraParams: { endpoint: "global" }, + } as never), + ).toEqual({ + endpoint: "global", + tool_stream: true, + }); + + const explicit = { endpoint: "global", tool_stream: false }; + expect( + provider.prepareExtraParams?.({ + provider: "zai", + modelId: "glm-4.7", + extraParams: explicit, + } as never), + ).toBe(explicit); + }); }); diff --git a/extensions/zai/index.ts b/extensions/zai/index.ts index 261a671c5c8..d3153c9c2e3 100644 --- a/extensions/zai/index.ts +++ b/extensions/zai/index.ts @@ -21,6 +21,7 @@ import { normalizeModelCompat, } from "openclaw/plugin-sdk/provider-model-shared"; import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family"; +import { defaultToolStreamExtraParams } from "openclaw/plugin-sdk/provider-stream-shared"; import { fetchZaiUsage, resolveLegacyPiAgentAccessToken } from "openclaw/plugin-sdk/provider-usage"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { detectZaiEndpoint, type ZaiEndpointId } from "./detect.js"; @@ -281,15 +282,7 @@ export default definePluginEntry({ ], resolveDynamicModel: (ctx) => resolveGlm5ForwardCompatModel(ctx), ...OPENAI_COMPATIBLE_REPLAY_HOOKS, - prepareExtraParams: (ctx) => { - if (ctx.extraParams?.tool_stream !== undefined) { - return ctx.extraParams; - } - return { - ...ctx.extraParams, - tool_stream: true, - }; - }, + prepareExtraParams: (ctx) => defaultToolStreamExtraParams(ctx.extraParams), ...ZAI_TOOL_STREAM_HOOKS, isBinaryThinking: () => true, isModernModelRef: ({ modelId }) => { diff --git a/src/plugin-sdk/provider-stream-shared.test.ts b/src/plugin-sdk/provider-stream-shared.test.ts index 6540ef44805..10e05ade4f5 100644 --- a/src/plugin-sdk/provider-stream-shared.test.ts +++ b/src/plugin-sdk/provider-stream-shared.test.ts @@ -2,6 +2,7 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; import { describe, expect, it } from "vitest"; import { createHtmlEntityToolCallArgumentDecodingWrapper, + defaultToolStreamExtraParams, decodeHtmlEntitiesInObject, } from "./provider-stream-shared.js"; @@ -42,6 +43,24 @@ describe("decodeHtmlEntitiesInObject", () => { }); }); +describe("defaultToolStreamExtraParams", () => { + it("defaults tool_stream on when absent", () => { + expect(defaultToolStreamExtraParams()).toEqual({ tool_stream: true }); + expect(defaultToolStreamExtraParams({ fastMode: true })).toEqual({ + fastMode: true, + tool_stream: true, + }); + }); + + it("preserves explicit tool_stream values", () => { + const enabled = { tool_stream: true, fastMode: true }; + const disabled = { tool_stream: false, fastMode: true }; + + expect(defaultToolStreamExtraParams(enabled)).toBe(enabled); + expect(defaultToolStreamExtraParams(disabled)).toBe(disabled); + }); +}); + describe("createHtmlEntityToolCallArgumentDecodingWrapper", () => { it("decodes tool call arguments in final and streaming messages", async () => { const resultMessage = { diff --git a/src/plugin-sdk/provider-stream-shared.ts b/src/plugin-sdk/provider-stream-shared.ts index c0e27c95355..97ec6d7e0b1 100644 --- a/src/plugin-sdk/provider-stream-shared.ts +++ b/src/plugin-sdk/provider-stream-shared.ts @@ -17,6 +17,18 @@ export function composeProviderStreamWrappers( ); } +export function defaultToolStreamExtraParams( + extraParams?: Record, +): Record { + if (extraParams?.tool_stream !== undefined) { + return extraParams; + } + return { + ...extraParams, + tool_stream: true, + }; +} + const HTML_ENTITY_RE = /&(?:amp|lt|gt|quot|apos|#39|#x[0-9a-f]+|#\d+);/i; function decodeHtmlEntities(value: string): string { diff --git a/src/plugin-sdk/provider-stream.ts b/src/plugin-sdk/provider-stream.ts index 70085e731e5..212af939efb 100644 --- a/src/plugin-sdk/provider-stream.ts +++ b/src/plugin-sdk/provider-stream.ts @@ -37,6 +37,7 @@ export { createMoonshotThinkingWrapper, createToolStreamWrapper, createZaiToolStreamWrapper, + defaultToolStreamExtraParams, hasCopilotVisionInput, isAnthropicBedrockModel, type ProviderStreamWrapperFactory,