perf(app): reduce startup websocket work
Make provider catalogs compact and content-addressed so repeat launches can reuse the persisted snapshot instead of transferring it again. Remove eager global catalog loading and the stopped-agent defensive tail fetch, while keeping sequence-gap and reconnect recovery as the correctness paths. Add an opt-in production-ID Android profiling build for local startup traces.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ProviderSnapshotEntry } from "@getpaseo/protocol/agent-types";
|
||||
import { compactProviderSnapshot } from "@getpaseo/protocol/provider-snapshot-codec";
|
||||
import { createProviderSnapshotCache } from "./provider-snapshot-cache";
|
||||
|
||||
function createStorage() {
|
||||
const values = new Map<string, string>();
|
||||
return {
|
||||
values,
|
||||
async getItem(key: string) {
|
||||
return values.get(key) ?? null;
|
||||
},
|
||||
async setItem(key: string, value: string) {
|
||||
values.set(key, value);
|
||||
},
|
||||
async removeItem(key: string) {
|
||||
values.delete(key);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("provider snapshot cache", () => {
|
||||
it("round-trips compact snapshots with shared thinking option references", async () => {
|
||||
const thinkingOptions = [{ id: "high", label: "High", isDefault: true }];
|
||||
const entries: ProviderSnapshotEntry[] = [
|
||||
{
|
||||
provider: "pi",
|
||||
status: "ready",
|
||||
enabled: true,
|
||||
models: ["one", "two"].map((id) => ({
|
||||
provider: "pi",
|
||||
id,
|
||||
label: id,
|
||||
thinkingOptions,
|
||||
defaultThinkingOptionId: "high",
|
||||
})),
|
||||
},
|
||||
];
|
||||
const storage = createStorage();
|
||||
const cache = createProviderSnapshotCache(storage);
|
||||
|
||||
await cache.write({
|
||||
serverId: "server-1",
|
||||
cwd: "/repo",
|
||||
hash: "snapshot-hash",
|
||||
generatedAt: "2026-08-04T00:00:00.000Z",
|
||||
compactSnapshot: compactProviderSnapshot(entries),
|
||||
});
|
||||
const cached = await cache.read("server-1", "/repo");
|
||||
|
||||
expect(cached?.entries).toEqual(entries);
|
||||
expect(cached?.entries[0]?.models?.[0]?.thinkingOptions).toBe(
|
||||
cached?.entries[0]?.models?.[1]?.thinkingOptions,
|
||||
);
|
||||
});
|
||||
|
||||
it("discards an invalid cache record", async () => {
|
||||
const storage = createStorage();
|
||||
const cache = createProviderSnapshotCache(storage);
|
||||
storage.values.set('@paseo/provider-snapshot/v1:["server-1","/repo"]', "not json");
|
||||
|
||||
await expect(cache.read("server-1", "/repo")).resolves.toBeNull();
|
||||
expect(storage.values.size).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import type { ProviderSnapshotEntry } from "@getpaseo/protocol/agent-types";
|
||||
import {
|
||||
expandProviderSnapshot,
|
||||
type CompactProviderSnapshot,
|
||||
} from "@getpaseo/protocol/provider-snapshot-codec";
|
||||
import { CompactProviderSnapshotSchema } from "@getpaseo/protocol/messages";
|
||||
|
||||
const CACHE_VERSION = 1;
|
||||
const CACHE_KEY_PREFIX = "@paseo/provider-snapshot/v1";
|
||||
|
||||
interface ProviderSnapshotStorage {
|
||||
getItem(key: string): Promise<string | null>;
|
||||
setItem(key: string, value: string): Promise<void>;
|
||||
removeItem(key: string): Promise<void>;
|
||||
}
|
||||
|
||||
interface StoredProviderSnapshot {
|
||||
version: typeof CACHE_VERSION;
|
||||
hash: string;
|
||||
generatedAt: string;
|
||||
compactSnapshot: CompactProviderSnapshot;
|
||||
}
|
||||
|
||||
export interface CachedProviderSnapshot extends StoredProviderSnapshot {
|
||||
entries: ProviderSnapshotEntry[];
|
||||
}
|
||||
|
||||
export interface ProviderSnapshotCache {
|
||||
read(serverId: string, cwd: string | null): Promise<CachedProviderSnapshot | null>;
|
||||
write(input: {
|
||||
serverId: string;
|
||||
cwd: string | null;
|
||||
hash: string;
|
||||
generatedAt: string;
|
||||
compactSnapshot: CompactProviderSnapshot;
|
||||
}): Promise<void>;
|
||||
}
|
||||
|
||||
export class ProviderSnapshotCacheMissError extends Error {
|
||||
constructor() {
|
||||
super("Daemon returned not-modified without a cached provider snapshot");
|
||||
this.name = "ProviderSnapshotCacheMissError";
|
||||
}
|
||||
}
|
||||
|
||||
function cacheKey(serverId: string, cwd: string | null): string {
|
||||
return `${CACHE_KEY_PREFIX}:${JSON.stringify([serverId, cwd])}`;
|
||||
}
|
||||
|
||||
function parseStoredProviderSnapshot(value: string): StoredProviderSnapshot | null {
|
||||
const parsed: unknown = JSON.parse(value);
|
||||
if (typeof parsed !== "object" || parsed === null) return null;
|
||||
const version = Reflect.get(parsed, "version");
|
||||
const hash = Reflect.get(parsed, "hash");
|
||||
const generatedAt = Reflect.get(parsed, "generatedAt");
|
||||
const compactSnapshot = CompactProviderSnapshotSchema.safeParse(
|
||||
Reflect.get(parsed, "compactSnapshot"),
|
||||
);
|
||||
if (
|
||||
version !== CACHE_VERSION ||
|
||||
typeof hash !== "string" ||
|
||||
typeof generatedAt !== "string" ||
|
||||
!compactSnapshot.success
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
version,
|
||||
hash,
|
||||
generatedAt,
|
||||
compactSnapshot: compactSnapshot.data,
|
||||
};
|
||||
}
|
||||
|
||||
async function discardCachedSnapshot(storage: ProviderSnapshotStorage, key: string): Promise<void> {
|
||||
try {
|
||||
await storage.removeItem(key);
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error)) throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function createProviderSnapshotCache(
|
||||
storage: ProviderSnapshotStorage,
|
||||
): ProviderSnapshotCache {
|
||||
return {
|
||||
async read(serverId, cwd) {
|
||||
const key = cacheKey(serverId, cwd);
|
||||
let value: string | null;
|
||||
try {
|
||||
value = await storage.getItem(key);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) return null;
|
||||
throw error;
|
||||
}
|
||||
if (value === null) return null;
|
||||
try {
|
||||
const stored = parseStoredProviderSnapshot(value);
|
||||
if (!stored) {
|
||||
await discardCachedSnapshot(storage, key);
|
||||
return null;
|
||||
}
|
||||
return { ...stored, entries: expandProviderSnapshot(stored.compactSnapshot) };
|
||||
} catch (error) {
|
||||
if (!(error instanceof SyntaxError) && !(error instanceof RangeError)) {
|
||||
throw error;
|
||||
}
|
||||
await discardCachedSnapshot(storage, key);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async write(input) {
|
||||
const stored: StoredProviderSnapshot = {
|
||||
version: CACHE_VERSION,
|
||||
hash: input.hash,
|
||||
generatedAt: input.generatedAt,
|
||||
compactSnapshot: input.compactSnapshot,
|
||||
};
|
||||
try {
|
||||
await storage.setItem(cacheKey(input.serverId, input.cwd), JSON.stringify(stored));
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error)) throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const providerSnapshotCache = createProviderSnapshotCache(AsyncStorage);
|
||||
@@ -21,11 +21,13 @@ export function providersSnapshotQueryKey(serverId: string | null, cwd?: string
|
||||
export function providersSnapshotRequestOptions(input: {
|
||||
cwd?: string | null;
|
||||
providers?: AgentProvider[];
|
||||
ifNoneMatch?: string;
|
||||
}) {
|
||||
const normalizedCwd = normalizeProvidersSnapshotCwd(input.cwd);
|
||||
return {
|
||||
...(normalizedCwd ? { cwd: normalizedCwd } : {}),
|
||||
...(input.providers ? { providers: input.providers } : {}),
|
||||
...(input.ifNoneMatch ? { ifNoneMatch: input.ifNoneMatch } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,12 @@ import { agentCommandsQueryRoot } from "@/hooks/agent-commands-query";
|
||||
import { orderCheckoutDiffFiles } from "@/git/diff-order";
|
||||
import { daemonConfigQueryKey } from "@/data/daemon-config";
|
||||
import { daemonPairingOfferQueryKey } from "@/data/daemon-pairing";
|
||||
import { providersSnapshotQueryKey, providersSnapshotQueryRoot } from "@/data/providers-snapshot";
|
||||
import { providerSnapshotCache, type ProviderSnapshotCache } from "@/data/provider-snapshot-cache";
|
||||
import {
|
||||
normalizeProvidersSnapshotCwd,
|
||||
providersSnapshotQueryKey,
|
||||
providersSnapshotQueryRoot,
|
||||
} from "@/data/providers-snapshot";
|
||||
|
||||
type ProvidersSnapshotUpdateMessage = Extract<
|
||||
SessionOutboundMessage,
|
||||
@@ -184,6 +189,7 @@ export function applyProvidersSnapshotUpdate(input: {
|
||||
serverId: string;
|
||||
queryClient: QueryClient;
|
||||
message: ProvidersSnapshotUpdate;
|
||||
cache?: ProviderSnapshotCache;
|
||||
}): void {
|
||||
if (input.message.type !== "providers_snapshot_update") {
|
||||
return;
|
||||
@@ -194,6 +200,16 @@ export function applyProvidersSnapshotUpdate(input: {
|
||||
generatedAt: input.message.payload.generatedAt,
|
||||
requestId: "providers_snapshot_update",
|
||||
});
|
||||
const { compactSnapshot, snapshotHash } = input.message.payload;
|
||||
if (compactSnapshot && snapshotHash) {
|
||||
void (input.cache ?? providerSnapshotCache).write({
|
||||
serverId: input.serverId,
|
||||
cwd: normalizeProvidersSnapshotCwd(input.message.payload.cwd),
|
||||
hash: snapshotHash,
|
||||
generatedAt: input.message.payload.generatedAt,
|
||||
compactSnapshot,
|
||||
});
|
||||
}
|
||||
void input.queryClient.invalidateQueries({
|
||||
queryKey: agentCommandsQueryRoot(input.serverId),
|
||||
exact: false,
|
||||
|
||||
Reference in New Issue
Block a user