perf(copilot): avoid duplicating BYOK request bodies (#131145)

This commit is contained in:
Peter Steinberger
2026-08-27 12:51:01 -07:00
committed by GitHub
parent 9be3cefabd
commit 3f3ccd2b40
2 changed files with 74 additions and 8 deletions
+72
View File
@@ -15,6 +15,7 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => ({
describe("createCopilotByokProxy", () => {
afterEach(() => {
ssrfRuntimeMock.fetchWithSsrFGuard.mockReset();
vi.restoreAllMocks();
});
it("presents a loopback SDK endpoint and forwards through guarded fetch", async () => {
@@ -82,6 +83,77 @@ describe("createCopilotByokProxy", () => {
}
});
it.each([307, 308])("preserves binary request bytes across a %i redirect", async (status) => {
const { fetchWithSsrFGuard } = await vi.importActual<
typeof import("openclaw/plugin-sdk/ssrf-runtime")
>("openclaw/plugin-sdk/ssrf-runtime");
ssrfRuntimeMock.fetchWithSsrFGuard.mockImplementation(fetchWithSsrFGuard);
const clientFetch = globalThis.fetch;
const received: Buffer[] = [];
const upstreamFetch = vi.spyOn(globalThis, "fetch").mockImplementation(async (url, init) => {
const request = new Request(url, init);
expect(request.method).toBe("POST");
received.push(Buffer.from(await request.arrayBuffer()));
return received.length === 1
? new Response(null, { status, headers: { location: "/v1/replayed" } })
: new Response("ok");
});
const proxy = await createCopilotByokProxy(
resolveCopilotProvider({
model: {
provider: "custom-proxy",
api: "openai-responses",
id: "proxy-model",
baseUrl: "https://proxy.example/v1",
},
}),
);
// Keep invalid UTF-8 and a nonzero offset: forwarding the backing pool would leak other bytes.
const body = Buffer.from([42, 0, 255, 128, 192, 10, 42]).subarray(1, -1);
try {
const response = await clientFetch(`${proxy?.provider.provider?.baseUrl}/responses`, {
method: "POST",
body,
});
expect(response.status).toBe(200);
expect(await response.text()).toBe("ok");
expect(upstreamFetch).toHaveBeenCalledTimes(2);
expect(received).toEqual([body, body]);
} finally {
await proxy?.close();
}
});
it.each(["GET", "HEAD", "POST"])(
"forwards an empty %s request without a body",
async (method) => {
ssrfRuntimeMock.fetchWithSsrFGuard.mockResolvedValue({
response: new Response(null, { status: 204 }),
release: vi.fn(async () => undefined),
});
const proxy = await createCopilotByokProxy(
resolveCopilotProvider({
model: {
provider: "custom-proxy",
api: "openai-responses",
id: "proxy-model",
baseUrl: "https://proxy.example/v1",
},
}),
);
try {
const response = await fetch(`${proxy?.provider.provider?.baseUrl}/responses`, { method });
expect(response.status).toBe(204);
expect(ssrfRuntimeMock.fetchWithSsrFGuard).toHaveBeenCalledWith(
expect.objectContaining({ init: expect.objectContaining({ method }) }),
);
expect(ssrfRuntimeMock.fetchWithSsrFGuard.mock.calls[0]?.[0].init.body).toBeUndefined();
} finally {
await proxy?.close();
}
},
);
it("injects resolved bearer auth when the SDK request omits Authorization", async () => {
ssrfRuntimeMock.fetchWithSsrFGuard.mockResolvedValue({
response: new Response("ok", { status: 200 }),
+2 -8
View File
@@ -126,7 +126,7 @@ async function handleProxyRequest(
: undefined,
}),
signal: upstreamAbort.signal,
...(body ? { body: toFetchBody(body) } : {}),
...(body ? { body } : {}),
},
auditContext: "copilot-byok-provider",
requireHttps: true,
@@ -210,7 +210,7 @@ function isNonceProtectedProxyRequest(req: IncomingMessage, proxyPathPrefix: str
);
}
async function readBody(req: IncomingMessage): Promise<Buffer | undefined> {
async function readBody(req: IncomingMessage): Promise<Buffer<ArrayBuffer> | undefined> {
const chunks: Buffer[] = [];
for await (const chunk of req) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -218,12 +218,6 @@ async function readBody(req: IncomingMessage): Promise<Buffer | undefined> {
return chunks.length > 0 ? Buffer.concat(chunks) : undefined;
}
function toFetchBody(body: Buffer): Uint8Array<ArrayBuffer> {
const copy = new Uint8Array(body.byteLength);
copy.set(body);
return copy;
}
function normalizeProxyRequestHeaders(headers: IncomingMessage["headers"]): Record<string, string> {
const out: Record<string, string> = {};
for (const [key, value] of Object.entries(headers)) {