fix(plugins): share tool-stream defaults and align xai sdk imports

This commit is contained in:
Vincent Koc
2026-04-14 16:23:29 +01:00
parent 50e5f95cc6
commit 9c42e6424d
17 changed files with 100 additions and 35 deletions

View File

@@ -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 (

View File

@@ -88,4 +88,28 @@ describe("xai provider plugin", () => {
(capturedPayload?.tools as Array<{ function?: Record<string, unknown> }>)[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);
});
});

View File

@@ -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

View File

@@ -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";

View File

@@ -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(

View File

@@ -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({

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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<string, unknown>;

View File

@@ -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,

View File

@@ -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<string, string>([
["grok-3", "grok-3-fast"],

View File

@@ -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);
});
});

View File

@@ -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 }) => {

View File

@@ -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 = {

View File

@@ -17,6 +17,18 @@ export function composeProviderStreamWrappers(
);
}
export function defaultToolStreamExtraParams(
extraParams?: Record<string, unknown>,
): Record<string, unknown> {
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 {

View File

@@ -37,6 +37,7 @@ export {
createMoonshotThinkingWrapper,
createToolStreamWrapper,
createZaiToolStreamWrapper,
defaultToolStreamExtraParams,
hasCopilotVisionInput,
isAnthropicBedrockModel,
type ProviderStreamWrapperFactory,