mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(xai): preserve all response text blocks and citations (#118477)
This commit is contained in:
committed by
GitHub
parent
59cd041c37
commit
8a5cfa4cac
@@ -165,6 +165,60 @@ describe("xai code_execution tool", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns every response answer and citation from the code execution HTTP boundary", async () => {
|
||||
const mockFetch = installCodeExecutionFetch({
|
||||
output: [
|
||||
{ type: "code_interpreter_call" },
|
||||
{
|
||||
type: "message",
|
||||
content: [
|
||||
{
|
||||
type: "output_text",
|
||||
text: "Mean: ",
|
||||
annotations: [{ type: "url_citation", url: "https://example.com/input.csv" }],
|
||||
},
|
||||
{
|
||||
type: "output_text",
|
||||
text: "42",
|
||||
annotations: [
|
||||
{ type: "url_citation", url: "https://example.com/result.csv" },
|
||||
{ type: "url_citation", url: "https://example.com/input.csv" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
content: [{ type: "output_text", text: ". Verified." }],
|
||||
},
|
||||
],
|
||||
});
|
||||
const tool = createCodeExecutionTool({
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
xai: {
|
||||
config: {
|
||||
webSearch: { apiKey: "xai-plugin-key" }, // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await tool?.execute?.("code-execution:multi-block", {
|
||||
task: "Calculate and verify the mean.",
|
||||
});
|
||||
|
||||
expect(firstFetchUrl(mockFetch)).toContain("api.x.ai/v1/responses");
|
||||
expect(result?.details).toMatchObject({
|
||||
content: "Mean: 42. Verified.",
|
||||
citations: ["https://example.com/input.csv", "https://example.com/result.csv"],
|
||||
usedCodeExecution: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses the xAI plugin web search key for code_execution requests", async () => {
|
||||
const mockFetch = installCodeExecutionFetch();
|
||||
const tool = createCodeExecutionTool({
|
||||
|
||||
@@ -67,6 +67,49 @@ describe("xai responses tool helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("collects every response text block and deduplicates citations across output items", () => {
|
||||
expect(
|
||||
requireXaiResponseTextAndCitations(
|
||||
{
|
||||
output: [
|
||||
{ type: "web_search_call" },
|
||||
{
|
||||
type: "message",
|
||||
content: [
|
||||
{
|
||||
type: "output_text",
|
||||
text: "First ",
|
||||
annotations: [{ type: "url_citation", url: "https://example.com/a" }],
|
||||
},
|
||||
{
|
||||
type: "output_text",
|
||||
text: "second",
|
||||
annotations: [
|
||||
{ type: "url_citation", url: "https://example.com/b" },
|
||||
{ type: "url_citation", url: "https://example.com/a" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "output_text",
|
||||
text: " and ",
|
||||
annotations: [{ type: "url_citation", url: "https://example.com/c" }],
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
content: [{ type: "output_text", text: "third" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
"xAI tool failed",
|
||||
),
|
||||
).toEqual({
|
||||
content: "First second and third",
|
||||
citations: ["https://example.com/a", "https://example.com/b", "https://example.com/c"],
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores malformed output, content, and annotation entries", () => {
|
||||
expect(
|
||||
extractXaiWebSearchContent({
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Xai plugin module implements responses tool shared behavior.
|
||||
import {
|
||||
isRecord,
|
||||
normalizeOptionalString as trimString,
|
||||
uniqueStrings,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { XaiWebSearchResponse } from "./web-search-response.types.js";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object";
|
||||
}
|
||||
|
||||
function extractUrlCitations(annotations: unknown): string[] {
|
||||
if (!Array.isArray(annotations)) {
|
||||
return [];
|
||||
@@ -51,32 +48,34 @@ export function extractXaiWebSearchContent(data: XaiWebSearchResponse): {
|
||||
text: string | undefined;
|
||||
annotationCitations: string[];
|
||||
} {
|
||||
const textParts: string[] = [];
|
||||
const annotationCitations: string[] = [];
|
||||
for (const output of data.output ?? []) {
|
||||
if (!isRecord(output)) {
|
||||
continue;
|
||||
}
|
||||
if (output.type === "message") {
|
||||
const content = Array.isArray(output.content) ? output.content : [];
|
||||
for (const block of content) {
|
||||
if (!isRecord(block)) {
|
||||
continue;
|
||||
}
|
||||
if (block.type === "output_text" && typeof block.text === "string" && block.text) {
|
||||
const urls = extractUrlCitations(block.annotations);
|
||||
return { text: block.text, annotationCitations: uniqueStrings(urls) };
|
||||
}
|
||||
const blocks =
|
||||
output.type === "message" && Array.isArray(output.content)
|
||||
? output.content
|
||||
: output.type === "output_text"
|
||||
? [output]
|
||||
: [];
|
||||
for (const block of blocks) {
|
||||
if (!isRecord(block) || block.type !== "output_text" || typeof block.text !== "string") {
|
||||
continue;
|
||||
}
|
||||
if (block.text) {
|
||||
textParts.push(block.text);
|
||||
annotationCitations.push(...extractUrlCitations(block.annotations));
|
||||
}
|
||||
}
|
||||
|
||||
if (output.type === "output_text" && typeof output.text === "string" && output.text) {
|
||||
const urls = extractUrlCitations(output.annotations);
|
||||
return { text: output.text, annotationCitations: uniqueStrings(urls) };
|
||||
}
|
||||
}
|
||||
|
||||
// Match the Responses SDK: adjacent output text blocks have no separator.
|
||||
const text = textParts.join("");
|
||||
return {
|
||||
text: typeof data.output_text === "string" ? data.output_text : undefined,
|
||||
annotationCitations: [],
|
||||
text: text || (typeof data.output_text === "string" ? data.output_text : undefined),
|
||||
annotationCitations: uniqueStrings(annotationCitations),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user