Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
655
extensions/xai/stream.test.ts
Normal file
655
extensions/xai/stream.test.ts
Normal file
@@ -0,0 +1,655 @@
|
||||
// Xai tests cover stream plugin behavior.
|
||||
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
|
||||
import { streamSimple, type Api, type Context, type Model } from "openclaw/plugin-sdk/llm";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyXaiRuntimeModelCompat } from "./runtime-model-compat.js";
|
||||
import {
|
||||
createXaiFastModeWrapper,
|
||||
createXaiToolPayloadCompatibilityWrapper,
|
||||
wrapXaiProviderStream,
|
||||
} from "./stream.js";
|
||||
import {
|
||||
createXaiPayloadCaptureStream,
|
||||
expectXaiFastToolStreamShaping,
|
||||
runXaiGrok4ResponseStream,
|
||||
} from "./test-helpers.js";
|
||||
type XaiStreamApi = Extract<Api, "openai-completions" | "openai-responses">;
|
||||
type StreamEvent = Record<string, unknown> & { type?: string };
|
||||
|
||||
async function collectEvents(stream: ReturnType<StreamFn>): Promise<StreamEvent[]> {
|
||||
const events: StreamEvent[] = [];
|
||||
for await (const event of stream as AsyncIterable<StreamEvent>) {
|
||||
events.push(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
function buildEventStreamFn(events: unknown[]): StreamFn {
|
||||
return (() =>
|
||||
({
|
||||
result: async () => {
|
||||
const done = events.find((event) => {
|
||||
const record = event && typeof event === "object" ? (event as { type?: unknown }) : {};
|
||||
return record.type === "done";
|
||||
}) as { message?: unknown } | undefined;
|
||||
return (done?.message ?? { role: "assistant", content: [] }) as never;
|
||||
},
|
||||
async *[Symbol.asyncIterator]() {
|
||||
for (const event of events) {
|
||||
yield event as never;
|
||||
}
|
||||
},
|
||||
}) as unknown as ReturnType<StreamFn>) as StreamFn;
|
||||
}
|
||||
|
||||
function captureWrappedModelId(params: {
|
||||
modelId: string;
|
||||
fastMode: boolean | (() => boolean | undefined);
|
||||
api?: XaiStreamApi;
|
||||
}): string {
|
||||
let capturedModelId = "";
|
||||
const baseStreamFn: StreamFn = (model) => {
|
||||
capturedModelId = model.id;
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
|
||||
const wrapped = createXaiFastModeWrapper(baseStreamFn, params.fastMode);
|
||||
void wrapped(
|
||||
{
|
||||
api: params.api ?? "openai-responses",
|
||||
provider: "xai",
|
||||
id: params.modelId,
|
||||
} as Model<Extract<Api, "openai-completions" | "openai-responses">>,
|
||||
{ messages: [] } as Context,
|
||||
{},
|
||||
);
|
||||
|
||||
return capturedModelId;
|
||||
}
|
||||
|
||||
function runXaiToolPayloadWrapper(params: {
|
||||
payload: Record<string, unknown>;
|
||||
api?: XaiStreamApi;
|
||||
modelId?: string;
|
||||
input?: string[];
|
||||
}) {
|
||||
const baseStreamFn: StreamFn = (_model, _context, options) => {
|
||||
options?.onPayload?.(params.payload, {} as Model<XaiStreamApi>);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
const wrapped = createXaiToolPayloadCompatibilityWrapper(baseStreamFn);
|
||||
const api = params.api ?? "openai-responses";
|
||||
|
||||
void wrapped(
|
||||
{
|
||||
api,
|
||||
provider: "xai",
|
||||
id:
|
||||
params.modelId ??
|
||||
(api === "openai-completions" ? "grok-4-1-fast-reasoning" : "grok-4-fast"),
|
||||
reasoning: params.modelId ? !params.modelId.includes("non-reasoning") : true,
|
||||
...(params.input ? { input: params.input } : {}),
|
||||
} as Model<XaiStreamApi>,
|
||||
{ messages: [] } as Context,
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
async function captureXaiResponsesPayloadWithThinking(): Promise<Record<string, unknown>> {
|
||||
const model = applyXaiRuntimeModelCompat({
|
||||
api: "openai-responses",
|
||||
provider: "xai",
|
||||
id: "grok-4.3",
|
||||
baseUrl: "https://api.x.ai/v1",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 1_000_000,
|
||||
maxTokens: 64_000,
|
||||
} as Model<"openai-responses">);
|
||||
|
||||
const payloadPromise = new Promise<Record<string, unknown>>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() => reject(new Error("provider payload callback was not invoked")),
|
||||
1_000,
|
||||
);
|
||||
const stream = streamSimple(
|
||||
model,
|
||||
{ messages: [{ role: "user", content: "hello", timestamp: 0 }] },
|
||||
{
|
||||
apiKey: "test-api-key",
|
||||
cacheRetention: "none",
|
||||
reasoning: "low",
|
||||
onPayload: (payload) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(structuredClone(payload as Record<string, unknown>));
|
||||
throw new Error("stop after payload capture");
|
||||
},
|
||||
},
|
||||
);
|
||||
void stream.result();
|
||||
});
|
||||
|
||||
return await payloadPromise;
|
||||
}
|
||||
|
||||
describe("xai stream wrappers", () => {
|
||||
it("rewrites supported Grok models to fast variants when fast mode is enabled", () => {
|
||||
expect(captureWrappedModelId({ modelId: "grok-3", fastMode: true })).toBe("grok-3-fast");
|
||||
expect(
|
||||
captureWrappedModelId({
|
||||
modelId: "grok-3",
|
||||
fastMode: true,
|
||||
api: "openai-completions",
|
||||
}),
|
||||
).toBe("grok-3-fast");
|
||||
expect(captureWrappedModelId({ modelId: "grok-4", fastMode: true })).toBe("grok-4-fast");
|
||||
expect(
|
||||
captureWrappedModelId({
|
||||
modelId: "grok-3",
|
||||
fastMode: true,
|
||||
api: "openai-responses",
|
||||
}),
|
||||
).toBe("grok-3-fast");
|
||||
});
|
||||
|
||||
it("leaves unsupported or disabled models unchanged", () => {
|
||||
expect(captureWrappedModelId({ modelId: "grok-3-fast", fastMode: true })).toBe("grok-3-fast");
|
||||
expect(captureWrappedModelId({ modelId: "grok-3", fastMode: false })).toBe("grok-3");
|
||||
});
|
||||
|
||||
it("resolves dynamic fast mode for each xai stream call", () => {
|
||||
const capturedModelIds: string[] = [];
|
||||
const baseStreamFn: StreamFn = (model) => {
|
||||
capturedModelIds.push(model.id);
|
||||
return {
|
||||
result: async () => ({}),
|
||||
async *[Symbol.asyncIterator]() {},
|
||||
} as unknown as ReturnType<StreamFn>;
|
||||
};
|
||||
let enabled = true;
|
||||
const wrapped = createXaiFastModeWrapper(baseStreamFn, () => enabled);
|
||||
const model = {
|
||||
api: "openai-responses",
|
||||
provider: "xai",
|
||||
id: "grok-4",
|
||||
} as Model<XaiStreamApi>;
|
||||
|
||||
void wrapped(model, { messages: [] } as Context, {});
|
||||
enabled = false;
|
||||
void wrapped(model, { messages: [] } as Context, {});
|
||||
|
||||
expect(capturedModelIds).toEqual(["grok-4-fast", "grok-4"]);
|
||||
});
|
||||
|
||||
it("composes the xai provider stream chain from extra params", () => {
|
||||
const capture = createXaiPayloadCaptureStream();
|
||||
|
||||
const wrapped = wrapXaiProviderStream({
|
||||
streamFn: capture.streamFn,
|
||||
extraParams: { fastMode: true },
|
||||
} as never);
|
||||
|
||||
runXaiGrok4ResponseStream(wrapped);
|
||||
expectXaiFastToolStreamShaping(capture);
|
||||
});
|
||||
|
||||
it("leaves tool-call argument html entities untouched, delegating decode to the core path", async () => {
|
||||
const toolCall = {
|
||||
type: "toolCall",
|
||||
id: "call_1",
|
||||
name: "write",
|
||||
arguments: { content: "&amp;" },
|
||||
};
|
||||
const assistant = {
|
||||
role: "assistant",
|
||||
content: [toolCall],
|
||||
stopReason: "toolUse",
|
||||
};
|
||||
const baseStream = buildEventStreamFn([
|
||||
{ type: "toolcall_end", contentIndex: 0, toolCall, partial: assistant },
|
||||
{ type: "done", reason: "toolUse", message: assistant },
|
||||
]);
|
||||
const wrapped = wrapXaiProviderStream({
|
||||
streamFn: baseStream,
|
||||
extraParams: { tool_stream: false },
|
||||
} as never);
|
||||
|
||||
const events = await collectEvents(
|
||||
wrapped!(
|
||||
{ api: "openai-responses", provider: "xai", id: "grok-4.3" } as Model<"openai-responses">,
|
||||
{ messages: [], tools: [] } as unknown as Context,
|
||||
{},
|
||||
),
|
||||
);
|
||||
|
||||
const done = events.find((event) => event.type === "done") as {
|
||||
message?: { content?: Array<{ arguments?: { content?: string } }> };
|
||||
};
|
||||
expect(done.message?.content?.[0]?.arguments?.content).toBe("&amp;");
|
||||
});
|
||||
|
||||
it("promotes standalone Grok-style tool text to a structured tool call", async () => {
|
||||
const rawToolText = '[tool:read] {"path":"/app/skills/meme-maker/SKILL.md"}';
|
||||
const baseStream = buildEventStreamFn([
|
||||
{ type: "start", partial: { content: [] } },
|
||||
{ type: "text_start", contentIndex: 0, partial: { content: [{ type: "text", text: "" }] } },
|
||||
{ type: "text_delta", contentIndex: 0, delta: rawToolText },
|
||||
{ type: "text_end", contentIndex: 0, content: rawToolText },
|
||||
{
|
||||
type: "done",
|
||||
reason: "stop",
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: rawToolText }],
|
||||
stopReason: "stop",
|
||||
},
|
||||
},
|
||||
]);
|
||||
const wrapped = wrapXaiProviderStream({
|
||||
streamFn: baseStream,
|
||||
extraParams: { tool_stream: false },
|
||||
} as never);
|
||||
|
||||
const events = await collectEvents(
|
||||
wrapped!(
|
||||
{
|
||||
api: "openai-responses",
|
||||
provider: "xai",
|
||||
id: "grok-4.3",
|
||||
} as Model<"openai-responses">,
|
||||
{
|
||||
messages: [],
|
||||
tools: [{ name: "read", description: "Read", parameters: { type: "object" } }],
|
||||
} as unknown as Context,
|
||||
{},
|
||||
),
|
||||
);
|
||||
|
||||
expect(events.map((event) => event.type)).toEqual([
|
||||
"start",
|
||||
"toolcall_start",
|
||||
"toolcall_delta",
|
||||
"done",
|
||||
]);
|
||||
const done = events.find((event) => event.type === "done") as {
|
||||
message?: { content?: Array<Record<string, unknown>>; stopReason?: string };
|
||||
reason?: string;
|
||||
};
|
||||
expect(done.reason).toBe("toolUse");
|
||||
expect(done.message?.stopReason).toBe("toolUse");
|
||||
expect(done.message?.content?.[0]).toMatchObject({
|
||||
type: "toolCall",
|
||||
name: "read",
|
||||
arguments: { path: "/app/skills/meme-maker/SKILL.md" },
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves dynamic fast mode in the composed xai provider stream chain", () => {
|
||||
const capturedModelIds: string[] = [];
|
||||
const baseStreamFn: StreamFn = (model) => {
|
||||
capturedModelIds.push(model.id);
|
||||
return {
|
||||
result: async () => ({}),
|
||||
async *[Symbol.asyncIterator]() {},
|
||||
} as unknown as ReturnType<StreamFn>;
|
||||
};
|
||||
let enabled = true;
|
||||
const wrapped = wrapXaiProviderStream({
|
||||
streamFn: baseStreamFn,
|
||||
extraParams: { fastMode: () => enabled },
|
||||
} as never);
|
||||
const model = {
|
||||
api: "openai-responses",
|
||||
provider: "xai",
|
||||
id: "grok-4",
|
||||
} as Model<XaiStreamApi>;
|
||||
|
||||
void wrapped?.(model, { messages: [] } as Context, {});
|
||||
enabled = false;
|
||||
void wrapped?.(model, { messages: [] } as Context, {});
|
||||
|
||||
expect(capturedModelIds).toEqual(["grok-4-fast", "grok-4"]);
|
||||
});
|
||||
|
||||
it("strips unsupported strict and reasoning controls from tool payloads", () => {
|
||||
const payload = {
|
||||
reasoning: "high",
|
||||
reasoningEffort: "high",
|
||||
reasoning_effort: "high",
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "write",
|
||||
parameters: { type: "object", properties: {} },
|
||||
strict: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
runXaiToolPayloadWrapper({
|
||||
payload,
|
||||
api: "openai-completions",
|
||||
modelId: "grok-4-fast-non-reasoning",
|
||||
});
|
||||
|
||||
expect(payload).not.toHaveProperty("reasoning");
|
||||
expect(payload).not.toHaveProperty("reasoningEffort");
|
||||
expect(payload).not.toHaveProperty("reasoning_effort");
|
||||
expect(payload.tools[0]?.function).not.toHaveProperty("strict");
|
||||
});
|
||||
|
||||
it("strips unsupported reasoning controls from non-reasoning xai payloads", () => {
|
||||
const payload: Record<string, unknown> = {
|
||||
reasoning: { effort: "high" },
|
||||
reasoningEffort: "high",
|
||||
reasoning_effort: "high",
|
||||
};
|
||||
runXaiToolPayloadWrapper({ payload, modelId: "grok-4-fast-non-reasoning" });
|
||||
|
||||
expect(payload).not.toHaveProperty("reasoning");
|
||||
expect(payload).not.toHaveProperty("reasoningEffort");
|
||||
expect(payload).not.toHaveProperty("reasoning_effort");
|
||||
});
|
||||
|
||||
it("passes reasoning controls through for reasoning-capable xai payloads", () => {
|
||||
const payload: Record<string, unknown> = {
|
||||
reasoning: { effort: "high" },
|
||||
reasoningEffort: "high",
|
||||
reasoning_effort: "high",
|
||||
};
|
||||
runXaiToolPayloadWrapper({ payload, modelId: "grok-4.3" });
|
||||
|
||||
expect(payload.reasoning).toEqual({ effort: "high" });
|
||||
expect(payload.reasoningEffort).toBe("high");
|
||||
expect(payload.reasoning_effort).toBe("high");
|
||||
});
|
||||
|
||||
it("strips reasoning controls when compat disables reasoning effort", () => {
|
||||
const payload: Record<string, unknown> = {
|
||||
reasoning: { effort: "high" },
|
||||
reasoningEffort: "high",
|
||||
reasoning_effort: "high",
|
||||
};
|
||||
const baseStreamFn: StreamFn = (model, _context, options) => {
|
||||
options?.onPayload?.(payload, model);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
const wrapped = createXaiToolPayloadCompatibilityWrapper(baseStreamFn);
|
||||
|
||||
void wrapped(
|
||||
{
|
||||
api: "openai-responses",
|
||||
provider: "xai",
|
||||
id: "grok-4.20-beta-latest-reasoning",
|
||||
reasoning: true,
|
||||
compat: { supportsReasoningEffort: false },
|
||||
} as unknown as Model<"openai-responses">,
|
||||
{ messages: [] } as Context,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(payload).not.toHaveProperty("reasoning");
|
||||
expect(payload).not.toHaveProperty("reasoningEffort");
|
||||
expect(payload).not.toHaveProperty("reasoning_effort");
|
||||
});
|
||||
|
||||
it("still requests encrypted reasoning include when effort is unsupported", () => {
|
||||
const payload: Record<string, unknown> = {
|
||||
reasoning: { effort: "high" },
|
||||
input: [],
|
||||
};
|
||||
const baseStreamFn: StreamFn = (model, _context, options) => {
|
||||
options?.onPayload?.(payload, model);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
const wrapped = createXaiToolPayloadCompatibilityWrapper(baseStreamFn);
|
||||
|
||||
void wrapped(
|
||||
{
|
||||
api: "openai-responses",
|
||||
provider: "xai",
|
||||
id: "grok-build-0.1",
|
||||
reasoning: true,
|
||||
compat: { supportsReasoningEffort: false },
|
||||
} as unknown as Model<"openai-responses">,
|
||||
{ messages: [] } as Context,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(payload).not.toHaveProperty("reasoning");
|
||||
expect(payload.include).toEqual(["reasoning.encrypted_content"]);
|
||||
});
|
||||
|
||||
it("merges encrypted reasoning include with existing include entries", () => {
|
||||
const payload: Record<string, unknown> = {
|
||||
include: ["file_search_call.results"],
|
||||
};
|
||||
runXaiToolPayloadWrapper({
|
||||
payload,
|
||||
modelId: "grok-build-0.1",
|
||||
});
|
||||
|
||||
expect(payload.include).toEqual(["file_search_call.results", "reasoning.encrypted_content"]);
|
||||
});
|
||||
|
||||
it("does not request encrypted reasoning include for non-reasoning xai models", () => {
|
||||
const payload: Record<string, unknown> = {};
|
||||
runXaiToolPayloadWrapper({
|
||||
payload,
|
||||
modelId: "grok-4-fast-non-reasoning",
|
||||
});
|
||||
|
||||
expect(payload).not.toHaveProperty("include");
|
||||
});
|
||||
|
||||
it("keeps native xAI Responses thinking efforts before the shared runtime dispatches payloads", async () => {
|
||||
const payload = await captureXaiResponsesPayloadWithThinking();
|
||||
|
||||
expect(payload.reasoning).toEqual({ effort: "low", summary: "auto" });
|
||||
expect(payload.include).toEqual(["reasoning.encrypted_content"]);
|
||||
});
|
||||
|
||||
it("moves image-bearing tool results out of function_call_output payloads", () => {
|
||||
const payload: Record<string, unknown> = {
|
||||
input: [
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_1",
|
||||
output: [
|
||||
{ type: "input_text", text: "Read image" },
|
||||
{
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: "data:image/png;base64,QUJDRA==",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
runXaiToolPayloadWrapper({ payload, input: ["text", "image"] });
|
||||
|
||||
expect(payload.input).toEqual([
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_1",
|
||||
output: "Read image",
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "input_text", text: "Attached image(s) from tool result:" },
|
||||
{
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: "data:image/png;base64,QUJDRA==",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("replays source-based input_image parts from tool results", () => {
|
||||
const payload: Record<string, unknown> = {
|
||||
input: [
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_1",
|
||||
output: [
|
||||
{ type: "input_text", text: "Read image" },
|
||||
{
|
||||
type: "input_image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/png",
|
||||
data: "QUJDRA==",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
runXaiToolPayloadWrapper({ payload, input: ["text", "image"] });
|
||||
|
||||
expect(payload.input).toEqual([
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_1",
|
||||
output: "Read image",
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "input_text", text: "Attached image(s) from tool result:" },
|
||||
{
|
||||
type: "input_image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/png",
|
||||
data: "QUJDRA==",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps multiple tool outputs contiguous before replaying collected images", () => {
|
||||
const payload: Record<string, unknown> = {
|
||||
input: [
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_1",
|
||||
output: [
|
||||
{ type: "input_text", text: "first" },
|
||||
{
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: "data:image/png;base64,QUFBQQ==",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_2",
|
||||
output: [
|
||||
{ type: "input_text", text: "second" },
|
||||
{
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: "data:image/png;base64,QkJCQg==",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
runXaiToolPayloadWrapper({ payload, input: ["text", "image"] });
|
||||
|
||||
expect(payload.input).toEqual([
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_1",
|
||||
output: "first",
|
||||
},
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_2",
|
||||
output: "second",
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "input_text", text: "Attached image(s) from tool result:" },
|
||||
{
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: "data:image/png;base64,QUFBQQ==",
|
||||
},
|
||||
{
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: "data:image/png;base64,QkJCQg==",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("drops image blocks and uses fallback text for models without image input", () => {
|
||||
const payload: Record<string, unknown> = {
|
||||
input: [
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_1",
|
||||
output: [
|
||||
{
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: "data:image/png;base64,QUJDRA==",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
runXaiToolPayloadWrapper({ payload, input: ["text"] });
|
||||
|
||||
expect(payload.input).toEqual([
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_1",
|
||||
output: "(see attached image)",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses audio fallback text for audio-only tool outputs", () => {
|
||||
const payload: Record<string, unknown> = {
|
||||
input: [
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_audio",
|
||||
output: [
|
||||
{
|
||||
type: "input_audio",
|
||||
mimeType: "audio/wav",
|
||||
data: "QUJDRA==",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
runXaiToolPayloadWrapper({ payload, input: ["text"] });
|
||||
|
||||
expect(payload.input).toEqual([
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "call_audio",
|
||||
output: "(see attached audio)",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user