Skip to content

Commit 0bb87da

Browse files
matt2eclaude
andauthored
perf: deduplicate _goose/providers/list RPC call at startup (#8873)
Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 56b8459 commit 0bb87da

3 files changed

Lines changed: 60 additions & 35 deletions

File tree

ui/goose2/src/app/hooks/useAppStartup.ts

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect } from "react";
22
import { useAgentStore } from "@/features/agents/stores/agentStore";
33
import { useChatSessionStore } from "@/features/chat/stores/chatSessionStore";
44
import { useProviderInventoryStore } from "@/features/providers/stores/providerInventoryStore";
5+
import { discoverAcpProvidersFromEntries } from "@/shared/api/acp";
56
import { setNotificationHandler, getClient } from "@/shared/api/acpConnection";
67
import notificationHandler from "@/shared/api/acpNotificationHandler";
78
import { perfLog } from "@/shared/lib/perfLog";
@@ -47,46 +48,41 @@ export function useAppStartup() {
4748
}
4849
};
4950

50-
const loadProviders = async () => {
51+
const loadProvidersAndInventory = async () => {
5152
const t0 = performance.now();
5253
store.setProvidersLoading(true);
53-
try {
54-
const { discoverAcpProviders } = await import("@/shared/api/acp");
55-
const providers = await discoverAcpProviders();
56-
store.setProviders(providers);
57-
perfLog(
58-
`[perf:startup] loadProviders done in ${(performance.now() - t0).toFixed(1)}ms (n=${providers.length})`,
59-
);
60-
} catch (err) {
61-
console.error("Failed to load ACP providers on startup:", err);
62-
} finally {
63-
store.setProvidersLoading(false);
64-
}
65-
};
66-
67-
const loadProviderInventory = async () => {
68-
const t0 = performance.now();
6954
inventoryStore.setLoading(true);
7055
try {
7156
const { getProviderInventory } = await import(
7257
"@/features/providers/api/inventory"
7358
);
7459
const entries = await getProviderInventory();
60+
61+
// Populate inventory store
7562
inventoryStore.setEntries(entries);
63+
64+
// Derive ACP providers from the same response
65+
const providers = discoverAcpProvidersFromEntries(entries);
66+
store.setProviders(providers);
67+
7668
perfLog(
77-
`[perf:startup] loadProviderInventory done in ${(performance.now() - t0).toFixed(1)}ms (n=${entries.length})`,
69+
`[perf:startup] loadProvidersAndInventory done in ${(performance.now() - t0).toFixed(1)}ms (entries=${entries.length}, providers=${providers.length})`,
7870
);
7971
return entries;
8072
} catch (err) {
81-
console.error("Failed to load provider inventory on startup:", err);
73+
console.error(
74+
"Failed to load providers and inventory on startup:",
75+
err,
76+
);
8277
return [];
8378
} finally {
79+
store.setProvidersLoading(false);
8480
inventoryStore.setLoading(false);
8581
}
8682
};
8783

8884
const refreshConfiguredProviderInventory = async (
89-
initialEntries?: Awaited<ReturnType<typeof loadProviderInventory>>,
85+
initialEntries?: Awaited<ReturnType<typeof loadProvidersAndInventory>>,
9086
) => {
9187
try {
9288
const entries =
@@ -146,15 +142,14 @@ export function useAppStartup() {
146142
setActiveSession(null);
147143
};
148144

149-
const inventoryLoad = loadProviderInventory();
145+
const providersAndInventoryLoad = loadProvidersAndInventory();
150146

151147
await Promise.allSettled([
152148
loadPersonas(),
153-
loadProviders(),
154-
inventoryLoad,
149+
providersAndInventoryLoad,
155150
loadSessionState(),
156151
]);
157-
void inventoryLoad.then((entries) =>
152+
void providersAndInventoryLoad.then((entries) =>
158153
refreshConfiguredProviderInventory(entries),
159154
);
160155
perfLog(

ui/goose2/src/shared/api/acp.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ export interface AcpCreateSessionOptions extends AcpPrepareSessionOptions {
3939
/** Discover ACP providers installed on the system. */
4040
export async function discoverAcpProviders(): Promise<AcpProvider[]> {
4141
const providers = await directAcp.listProviders();
42+
return resolveProvidersCatalog(providers);
43+
}
44+
45+
/**
46+
* Derive ACP providers from already-fetched inventory entries,
47+
* avoiding a duplicate `_goose/providers/list` RPC.
48+
*/
49+
export function discoverAcpProvidersFromEntries(
50+
entries: Array<{ providerId: string; providerName: string }>,
51+
): AcpProvider[] {
52+
return resolveProvidersCatalog(
53+
directAcp.buildProviderListFromEntries(entries),
54+
);
55+
}
56+
57+
function resolveProvidersCatalog(providers: AcpProvider[]): AcpProvider[] {
4258
const seen = new Set<string>();
4359

4460
return providers

ui/goose2/src/shared/api/acpApi.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,40 @@ export interface AcpSessionInfo {
2727
personaId: string | null;
2828
}
2929

30-
const DEPRECATED_PROVIDER_IDS = new Set(["claude-code", "codex", "gemini-cli"]);
31-
const DEFAULT_PROVIDER: AcpProvider = {
30+
export const DEPRECATED_PROVIDER_IDS = new Set([
31+
"claude-code",
32+
"codex",
33+
"gemini-cli",
34+
]);
35+
export const DEFAULT_PROVIDER: AcpProvider = {
3236
id: "goose",
3337
label: "Goose (Default)",
3438
};
3539

40+
/**
41+
* Build the ACP provider list from raw inventory entries.
42+
*
43+
* Shared by both `listProviders` (which fetches entries via RPC) and
44+
* `discoverAcpProvidersFromEntries` in acp.ts (which reuses
45+
* already-fetched entries at startup).
46+
*/
47+
export function buildProviderListFromEntries(
48+
entries: Array<{ providerId: string; providerName: string }>,
49+
): AcpProvider[] {
50+
return [
51+
DEFAULT_PROVIDER,
52+
...entries
53+
.filter((entry) => !DEPRECATED_PROVIDER_IDS.has(entry.providerId))
54+
.map((entry) => ({ id: entry.providerId, label: entry.providerName })),
55+
];
56+
}
57+
3658
export async function listProviders(): Promise<AcpProvider[]> {
3759
const client = await getClient();
3860
const result = await client.goose.GooseProvidersList({
3961
providerIds: [],
4062
});
41-
42-
const providers = result.entries
43-
.filter((entry) => !DEPRECATED_PROVIDER_IDS.has(entry.providerId))
44-
.map((entry) => ({
45-
id: entry.providerId,
46-
label: entry.providerName,
47-
}));
48-
49-
return [DEFAULT_PROVIDER, ...providers];
63+
return buildProviderListFromEntries(result.entries);
5064
}
5165

5266
export async function listSessions(): Promise<AcpSessionInfo[]> {

0 commit comments

Comments
 (0)