Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/web-search/xai-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { OcxProviderConfig } from "../types";
import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
import { fetchWithResetRetry } from "../lib/upstream-retry";
import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
import { readBoundedResponseBytes } from "../lib/bounded-body";
import { sidecarEnter } from "../lib/sidecar-tracker";
import { redactSecretString } from "../lib/redact";
import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
Expand Down Expand Up @@ -114,10 +115,19 @@ export async function runXaiWebSearch(
);
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
if (!res.ok) {
const t = await res.text().catch(() => "");
detachBodyGuard();
const entitlement = res.status === 401 || res.status === 403 ? " (Grok OAuth entitlement — re-run ocx login xai?)" : "";
return { text: "", sources: [], error: `xai sidecar HTTP ${res.status}${entitlement}: ${redactSecretString(t.slice(0, 200))}` };
try {
const bounded = await readBoundedResponseBytes(res, {
maxBytes: MAX_SIDECAR_RESPONSE_BYTES,
signal: linkedSignal.signal,
});
Comment on lines +119 to +122

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve HTTP status when bounded body reading fails

When xAI has already returned non-OK headers but its body stream subsequently rejects—for example, because the connection resets while reading—the new await propagates to the outer catch and replaces the known HTTP status and any 401/403 entitlement hint with a generic body-read/connect error. The previous res.text().catch(() => "") retained the status-only error in this scenario; catch read failures within the non-OK branch and fall back to that status-based representation while still propagating intentional aborts.

AGENTS.md reference: src/AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

const detail = bounded.oversized
? "response body exceeded byte bound"
: redactSecretString(new TextDecoder().decode(bounded.bytes).slice(0, 200));
const entitlement = res.status === 401 || res.status === 403 ? " (Grok OAuth entitlement — re-run ocx login xai?)" : "";
return { text: "", sources: [], error: `xai sidecar HTTP ${res.status}${entitlement}: ${detail}` };
} finally {
detachBodyGuard();
}
}
try {
return await parseXaiResponsesSSE(res);
Expand Down
28 changes: 28 additions & 0 deletions tests/xai-web-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,34 @@ describe("credential pinning + loop fail-closed (review blockers)", () => {
globalThis.fetch = realFetch;
}
});

test("non-OK response bodies are byte-bounded and canceled upstream", async () => {
let producedBytes = 0;
let canceled = false;
const chunk = new Uint8Array(1024).fill(0x61);
const realFetch = globalThis.fetch;
globalThis.fetch = (async () => new Response(new ReadableStream({
pull(controller) {
producedBytes += chunk.byteLength;
controller.enqueue(chunk);
},
cancel() {
canceled = true;
},
}), { status: 500 })) as typeof fetch;
try {
const { runXaiWebSearch } = await import("../src/web-search/xai-executor");
const out = await runXaiWebSearch("q", "xai", xaiProvider, { model: "grok-4.6", reasoning: "low", timeoutMs: 5000, describeImages: false });

expect(out.error).toContain("response body exceeded byte bound");
// The stream implementation may prefetch a small number of chunks, but it must
// stop near the cap rather than consume an arbitrarily large upstream body.
expect(producedBytes).toBeLessThanOrEqual(MAX_SIDECAR_RESPONSE_BYTES + (4 * chunk.byteLength));
expect(canceled).toBe(true);
} finally {
globalThis.fetch = realFetch;
}
});
});

import { runWithWebSearch, type WebSearchLoopDeps } from "../src/web-search/loop";
Expand Down
Loading