perf: speed up chat loading and resume

This commit is contained in:
Federico Jaramillo Martinez
2026-07-12 09:22:19 +02:00
parent 02f34c495c
commit 338faf4b81
25 changed files with 1565 additions and 83 deletions
+32
View File
@@ -29,6 +29,38 @@ describe("RemoteMachineClient", () => {
expect(new Headers(init.headers).get("content-type")).toBe("application/json");
expect(init.body).toBe(JSON.stringify({ cwd: "/repo" }));
});
it("requests compression for the remote hop even when configured headers use different casing", async () => {
const fetchImpl = vi.fn<typeof fetch>(() => Promise.resolve(new Response("ok", { status: 200 })));
const client = new RemoteMachineClient({
baseUrl: "https://remote.example.test/",
headers: { "Accept-Encoding": "identity" },
}, fetchImpl);
await client.request("GET", "/api/projects");
const { init } = onlyFetchCall(fetchImpl);
expect(new Headers(init.headers).get("accept-encoding")).toBe("gzip, deflate");
});
it("removes stale representation headers after Fetch decodes a compressed response", async () => {
const fetchImpl = vi.fn<typeof fetch>(() => Promise.resolve(new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: {
"content-type": "application/json",
"content-encoding": "gzip",
"content-length": "31",
},
})));
const client = new RemoteMachineClient({ baseUrl: "https://remote.example.test/" }, fetchImpl);
const response = await client.requestJson("GET", "/api/projects");
expect(response.body).toEqual({ ok: true });
expect(response.headers["content-type"]).toBe("application/json");
expect(response.headers["content-encoding"]).toBeUndefined();
expect(response.headers["content-length"]).toBeUndefined();
});
});
function fetchInputUrl(input: RequestInfo | URL): string {
+20 -10
View File
@@ -28,6 +28,8 @@ export interface MachineClient {
export const DEFAULT_REMOTE_REQUEST_TIMEOUT_MS = 30_000;
export const DEFAULT_REMOTE_HEALTH_TIMEOUT_MS = 3_000;
const REMOTE_RESPONSE_ACCEPT_ENCODING = "gzip, deflate";
const BLOCKED_CONFIGURED_HEADER_NAMES = new Set([
"host",
"connection",
@@ -57,7 +59,7 @@ export class RemoteMachineClient implements MachineClient {
const response = await this.fetchResponse(method, path, body, options);
return {
statusCode: response.status,
headers: headersToRecord(response.headers),
headers: decodedResponseHeaders(response.headers),
...(response.body === null ? {} : { body: readableFromWebResponseBody(response.body) }),
};
}
@@ -68,7 +70,7 @@ export class RemoteMachineClient implements MachineClient {
const parsed: unknown = text === "" ? undefined : JSON.parse(text);
return {
statusCode: response.status,
headers: headersToRecord(response.headers),
headers: decodedResponseHeaders(response.headers),
body: parsed,
};
}
@@ -100,12 +102,12 @@ export class RemoteMachineClient implements MachineClient {
}
}
private requestHeaders(body: unknown, options: MachineRequestOptions): HeadersInit {
return {
...this.remoteHeaders(),
accept: "*/*",
...(body === undefined ? {} : { "content-type": options.contentType ?? defaultContentTypeForBody(body) }),
};
private requestHeaders(body: unknown, options: MachineRequestOptions): Headers {
const headers = new Headers(this.remoteHeaders());
headers.set("accept", "*/*");
headers.set("accept-encoding", REMOTE_RESPONSE_ACCEPT_ENCODING);
if (body !== undefined) headers.set("content-type", options.contentType ?? defaultContentTypeForBody(body));
return headers;
}
private remoteHeaders(): Record<string, string> {
@@ -145,8 +147,16 @@ function filterConfiguredHeaders(headers: Record<string, string> | undefined): R
return Object.fromEntries(Object.entries(headers).filter(([key]) => !BLOCKED_CONFIGURED_HEADER_NAMES.has(key.toLowerCase())));
}
function headersToRecord(headers: Headers): Record<string, string> {
return Object.fromEntries(headers.entries());
function decodedResponseHeaders(headers: Headers): Record<string, string> {
const values: Record<string, string> = Object.fromEntries(headers.entries());
const contentEncoding = values["content-encoding"];
if (contentEncoding !== undefined && contentEncoding !== "identity") {
// Fetch decodes response bodies but retains headers for the encoded wire
// representation. The outer HTTP edge must negotiate and frame the decoded body.
delete values["content-encoding"];
delete values["content-length"];
}
return values;
}
function serializeRequestBody(method: string, body: unknown): NonNullable<RequestInit["body"]> | undefined {