fix: Grok web_search extracts output_text blocks at top level (openclaw#20508) thanks @echoVic
Verified: - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: echoVic <16428813+echoVic@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/Config: add canonical `--strict-json` parsing for `config set` and keep `--json` as a legacy alias to reduce help/behavior drift. (#21332) thanks @adhitShet.
|
||||
- CLI: keep `openclaw -v` as a root-only version alias so subcommand `-v, --verbose` flags (for example ACP/hooks/skills) are no longer intercepted globally. (#21303) thanks @adhitShet.
|
||||
- Config/Memory: restore schema help/label metadata for hybrid `mmr` and `temporalDecay` settings so configuration surfaces show correct names and guidance. (#18786) Thanks @rodrigouroz.
|
||||
- Tools/web_search: handle xAI Responses API payloads that emit top-level `output_text` blocks (without a `message` wrapper) so Grok web_search no longer returns `No response` for those results. (#20508) Thanks @echoVic.
|
||||
|
||||
- Discord/Gateway: handle close code 4014 (missing privileged gateway intents) without crashing the gateway. Thanks @thewilloftheshadow.
|
||||
- Security/Net: strip sensitive headers (`Authorization`, `Proxy-Authorization`, `Cookie`, `Cookie2`) on cross-origin redirects in `fetchWithSsrFGuard` to prevent credential forwarding across origin boundaries. (#20313) Thanks @afurm.
|
||||
|
||||
@@ -219,4 +219,26 @@ describe("web_search grok response parsing", () => {
|
||||
expect(result.text).toBeUndefined();
|
||||
expect(result.annotationCitations).toEqual([]);
|
||||
});
|
||||
|
||||
it("extracts output_text blocks directly in output array (no message wrapper)", () => {
|
||||
const result = extractGrokContent({
|
||||
output: [
|
||||
{ type: "web_search_call" },
|
||||
{
|
||||
type: "output_text",
|
||||
text: "direct output text",
|
||||
annotations: [
|
||||
{
|
||||
type: "url_citation",
|
||||
url: "https://example.com/direct",
|
||||
start_index: 0,
|
||||
end_index: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as Parameters<typeof extractGrokContent>[0]);
|
||||
expect(result.text).toBe("direct output text");
|
||||
expect(result.annotationCitations).toEqual(["https://example.com/direct"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,6 +106,7 @@ type GrokSearchResponse = {
|
||||
output?: Array<{
|
||||
type?: string;
|
||||
role?: string;
|
||||
text?: string; // present when type === "output_text" (top-level output_text block)
|
||||
content?: Array<{
|
||||
type?: string;
|
||||
text?: string;
|
||||
@@ -116,6 +117,12 @@ type GrokSearchResponse = {
|
||||
end_index?: number;
|
||||
}>;
|
||||
}>;
|
||||
annotations?: Array<{
|
||||
type?: string;
|
||||
url?: string;
|
||||
start_index?: number;
|
||||
end_index?: number;
|
||||
}>;
|
||||
}>;
|
||||
output_text?: string; // deprecated field - kept for backwards compatibility
|
||||
citations?: string[];
|
||||
@@ -143,18 +150,33 @@ function extractGrokContent(data: GrokSearchResponse): {
|
||||
} {
|
||||
// xAI Responses API format: find the message output with text content
|
||||
for (const output of data.output ?? []) {
|
||||
if (output.type !== "message") {
|
||||
continue;
|
||||
}
|
||||
for (const block of output.content ?? []) {
|
||||
if (block.type === "output_text" && typeof block.text === "string" && block.text) {
|
||||
// Extract url_citation annotations from this content block
|
||||
const urls = (block.annotations ?? [])
|
||||
.filter((a) => a.type === "url_citation" && typeof a.url === "string")
|
||||
.map((a) => a.url as string);
|
||||
return { text: block.text, annotationCitations: [...new Set(urls)] };
|
||||
if (output.type === "message") {
|
||||
for (const block of output.content ?? []) {
|
||||
if (block.type === "output_text" && typeof block.text === "string" && block.text) {
|
||||
const urls = (block.annotations ?? [])
|
||||
.filter((a) => a.type === "url_citation" && typeof a.url === "string")
|
||||
.map((a) => a.url as string);
|
||||
return { text: block.text, annotationCitations: [...new Set(urls)] };
|
||||
}
|
||||
}
|
||||
}
|
||||
// Some xAI responses place output_text blocks directly in the output array
|
||||
// without a message wrapper.
|
||||
if (
|
||||
output.type === "output_text" &&
|
||||
"text" in output &&
|
||||
typeof output.text === "string" &&
|
||||
output.text
|
||||
) {
|
||||
const rawAnnotations =
|
||||
"annotations" in output && Array.isArray(output.annotations) ? output.annotations : [];
|
||||
const urls = rawAnnotations
|
||||
.filter(
|
||||
(a: Record<string, unknown>) => a.type === "url_citation" && typeof a.url === "string",
|
||||
)
|
||||
.map((a: Record<string, unknown>) => a.url as string);
|
||||
return { text: output.text, annotationCitations: [...new Set(urls)] };
|
||||
}
|
||||
}
|
||||
// Fallback: deprecated output_text field
|
||||
const text = typeof data.output_text === "string" ? data.output_text : undefined;
|
||||
|
||||
Reference in New Issue
Block a user