mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
refresh onboarding when provider catalog loads (#9051)
Signed-off-by: tulsi <tulsi@block.xyz>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { act, renderHook } from "@testing-library/react";
|
||||
import { act, renderHook, waitFor } from "@testing-library/react";
|
||||
import type { ProviderInventoryEntryDto } from "@aaif/goose-sdk";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { useAgentStore } from "@/features/agents/stores/agentStore";
|
||||
@@ -90,6 +90,35 @@ describe("useOnboardingGate", () => {
|
||||
expect(result.current.readiness.reason).toBe("ready");
|
||||
});
|
||||
|
||||
it("recomputes model readiness when the provider catalog loads after inventory", async () => {
|
||||
useProviderCatalogStore.getState().reset();
|
||||
writeCompletedOnboarding("anthropic", "claude-sonnet-4-5");
|
||||
useProviderInventoryStore.getState().setEntries([providerEntry({})]);
|
||||
|
||||
const { result } = renderHook(() => useOnboardingGate(true));
|
||||
|
||||
expect(result.current.shouldShowOnboarding).toBe(true);
|
||||
expect(result.current.readiness.reason).toBe("missing_provider");
|
||||
|
||||
act(() => {
|
||||
useProviderCatalogStore.getState().setEntries([
|
||||
{
|
||||
id: "anthropic",
|
||||
displayName: "Anthropic",
|
||||
category: "model",
|
||||
description: "",
|
||||
setupMethod: "single_api_key",
|
||||
group: "default",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.shouldShowOnboarding).toBe(false);
|
||||
expect(result.current.readiness.providerId).toBe("anthropic");
|
||||
});
|
||||
});
|
||||
|
||||
it("skips onboarding when completion and the selected Goose model are usable", () => {
|
||||
writeCompletedOnboarding("anthropic", "claude-sonnet-4-5");
|
||||
setStoredModelPreference("goose", {
|
||||
|
||||
@@ -2,10 +2,11 @@ import { useCallback, useMemo, useState } from "react";
|
||||
import type { ProviderInventoryEntryDto } from "@aaif/goose-sdk";
|
||||
import { useAgentStore } from "@/features/agents/stores/agentStore";
|
||||
import {
|
||||
getModelProviders,
|
||||
resolveAgentProviderCatalogIdStrict,
|
||||
getModelProvidersFromEntries,
|
||||
resolveAgentProviderCatalogIdStrictFromEntries,
|
||||
} from "@/features/providers/providerCatalog";
|
||||
import { useProviderInventory } from "@/features/providers/hooks/useProviderInventory";
|
||||
import { useProviderCatalogStore } from "@/features/providers/stores/providerCatalogStore";
|
||||
import { useDistroStore } from "@/features/settings/stores/distroStore";
|
||||
import { filterModelProvidersForDistro } from "@/features/providers/distroProviderConstraints";
|
||||
import { getStoredModelPreference } from "@/features/chat/lib/modelPreferences";
|
||||
@@ -51,6 +52,7 @@ export function useOnboardingGate(startupReady: boolean) {
|
||||
const selectedProvider = useAgentStore((state) => state.selectedProvider);
|
||||
const { entries, configuredModelProviderEntries, getModelsForAgent } =
|
||||
useProviderInventory();
|
||||
const catalogEntries = useProviderCatalogStore((state) => state.entries);
|
||||
const distro = useDistroStore((state) => state.manifest);
|
||||
const [completion, setCompletion] = useState<OnboardingCompletion | null>(
|
||||
readCompletion,
|
||||
@@ -59,17 +61,24 @@ export function useOnboardingGate(startupReady: boolean) {
|
||||
const modelProviderIds = useMemo(
|
||||
() =>
|
||||
new Set(
|
||||
filterModelProvidersForDistro(getModelProviders(), distro).map(
|
||||
(provider) => provider.id,
|
||||
),
|
||||
filterModelProvidersForDistro(
|
||||
getModelProvidersFromEntries(catalogEntries),
|
||||
distro,
|
||||
).map((provider) => provider.id),
|
||||
),
|
||||
[distro],
|
||||
[catalogEntries, distro],
|
||||
);
|
||||
|
||||
const selectedAgentId = useMemo(
|
||||
() =>
|
||||
resolveAgentProviderCatalogIdStrictFromEntries(
|
||||
catalogEntries,
|
||||
selectedProvider,
|
||||
) ?? "goose",
|
||||
[catalogEntries, selectedProvider],
|
||||
);
|
||||
|
||||
const readiness = useMemo<OnboardingReadiness>(() => {
|
||||
const selectedAgentId =
|
||||
resolveAgentProviderCatalogIdStrict(selectedProvider) ?? "goose";
|
||||
|
||||
if (selectedAgentId !== "goose") {
|
||||
const models = getModelsForAgent(selectedAgentId);
|
||||
const entry = entries.get(selectedAgentId);
|
||||
@@ -136,7 +145,7 @@ export function useOnboardingGate(startupReady: boolean) {
|
||||
entries,
|
||||
getModelsForAgent,
|
||||
modelProviderIds,
|
||||
selectedProvider,
|
||||
selectedAgentId,
|
||||
]);
|
||||
|
||||
const completeOnboarding = useCallback(
|
||||
|
||||
@@ -158,6 +158,39 @@ describe("useOnboardingProviderStep", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("updates provider setup rows when the catalog loads after initial render", async () => {
|
||||
useProviderCatalogStore.getState().reset();
|
||||
|
||||
const { result } = renderProviderStep(
|
||||
readyReadiness({
|
||||
isUsable: false,
|
||||
providerId: null,
|
||||
reason: "not_completed",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.modelProviders).toHaveLength(0);
|
||||
|
||||
act(() => {
|
||||
useProviderCatalogStore.getState().setEntries([
|
||||
{
|
||||
id: "anthropic",
|
||||
displayName: "Anthropic",
|
||||
category: "model",
|
||||
description: "",
|
||||
setupMethod: "single_api_key",
|
||||
group: "default",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
result.current.modelProviders.map((provider) => provider.id),
|
||||
).toEqual(["anthropic"]),
|
||||
);
|
||||
});
|
||||
|
||||
it("continues with a current ACP agent without writing Goose model defaults", async () => {
|
||||
const { result, onReady, onSelectedSetup } = renderProviderStep(
|
||||
readyReadiness({
|
||||
|
||||
@@ -3,11 +3,12 @@ import type { ProviderInventoryEntryDto } from "@aaif/goose-sdk";
|
||||
import { useAgentStore } from "@/features/agents/stores/agentStore";
|
||||
import { setStoredModelPreference } from "@/features/chat/lib/modelPreferences";
|
||||
import {
|
||||
getAgentProviders,
|
||||
getModelProviders,
|
||||
getAgentProvidersFromEntries,
|
||||
getModelProvidersFromEntries,
|
||||
} from "@/features/providers/providerCatalog";
|
||||
import { filterModelProvidersForDistro } from "@/features/providers/distroProviderConstraints";
|
||||
import { useCredentials } from "@/features/providers/hooks/useCredentials";
|
||||
import { useProviderCatalogStore } from "@/features/providers/stores/providerCatalogStore";
|
||||
import { useProviderInventoryStore } from "@/features/providers/stores/providerInventoryStore";
|
||||
import { useDistroStore } from "@/features/settings/stores/distroStore";
|
||||
import { saveDefaults } from "../api/onboarding";
|
||||
@@ -42,6 +43,7 @@ export function useOnboardingProviderStep({
|
||||
);
|
||||
|
||||
const inventoryEntries = useProviderInventoryStore((state) => state.entries);
|
||||
const catalogEntries = useProviderCatalogStore((state) => state.entries);
|
||||
const agentStore = useAgentStore();
|
||||
const distro = useDistroStore((state) => state.manifest);
|
||||
|
||||
@@ -57,8 +59,16 @@ export function useOnboardingProviderStep({
|
||||
completeNativeSetup,
|
||||
} = useCredentials();
|
||||
|
||||
const agentProviders = useMemo(
|
||||
() => getAgentProvidersFromEntries(catalogEntries),
|
||||
[catalogEntries],
|
||||
);
|
||||
|
||||
const modelProviders = useMemo(() => {
|
||||
const all = filterModelProvidersForDistro(getModelProviders(), distro);
|
||||
const all = filterModelProvidersForDistro(
|
||||
getModelProvidersFromEntries(catalogEntries),
|
||||
distro,
|
||||
);
|
||||
return [...all].sort((a, b) => {
|
||||
const aIndex = PROMOTED_MODEL_ORDER.indexOf(a.id);
|
||||
const bIndex = PROMOTED_MODEL_ORDER.indexOf(b.id);
|
||||
@@ -67,7 +77,7 @@ export function useOnboardingProviderStep({
|
||||
}
|
||||
return a.displayName.localeCompare(b.displayName);
|
||||
});
|
||||
}, [distro]);
|
||||
}, [catalogEntries, distro]);
|
||||
|
||||
const visibleModelProviders = modelProviders.filter(
|
||||
(provider) =>
|
||||
@@ -93,12 +103,10 @@ export function useOnboardingProviderStep({
|
||||
(entry) =>
|
||||
entry.configured &&
|
||||
entry.providerId !== "goose" &&
|
||||
getAgentProviders().some(
|
||||
(provider) => provider.id === entry.providerId,
|
||||
) &&
|
||||
agentProviders.some((provider) => provider.id === entry.providerId) &&
|
||||
entry.models.length > 0,
|
||||
),
|
||||
[inventoryEntries],
|
||||
[agentProviders, inventoryEntries],
|
||||
);
|
||||
|
||||
const usableDefaultEntries = useMemo<UsableDefaultEntry[]>(
|
||||
@@ -167,7 +175,7 @@ export function useOnboardingProviderStep({
|
||||
modelId: readiness.modelId,
|
||||
modelName: readiness.modelName,
|
||||
};
|
||||
const isAgentProvider = getAgentProviders().some(
|
||||
const isAgentProvider = agentProviders.some(
|
||||
(provider) => provider.id === readiness.providerId,
|
||||
);
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"defaultFallback": "Ready on Home",
|
||||
"defaultDescription": "This is what Goose will use when you start a new chat.",
|
||||
"extensionsTitle": "Extensions",
|
||||
"extensionsDescription": "Connected tools are available in Settings. Imported Claude Desktop tools stay off until you enable them.",
|
||||
"extensionsDescription": "Goose can use connected tools in chats when needed. Manage imported tools from Extensions.",
|
||||
"skillsTitle": "Skills",
|
||||
"skillsDescription": "Personal skills Goose can see now. Project skills appear when you open a project."
|
||||
},
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"defaultFallback": "Listo en Inicio",
|
||||
"defaultDescription": "Esto es lo que Goose usará cuando empieces un chat nuevo.",
|
||||
"extensionsTitle": "Extensiones",
|
||||
"extensionsDescription": "Las herramientas conectadas están disponibles en Configuración. Las herramientas importadas de Claude Desktop quedan desactivadas hasta que las habilites.",
|
||||
"extensionsDescription": "Goose puede usar herramientas conectadas en los chats cuando las necesite. Administra las herramientas importadas desde Extensiones.",
|
||||
"skillsTitle": "Habilidades",
|
||||
"skillsDescription": "Habilidades personales que Goose puede ver ahora. Las habilidades de proyecto aparecen cuando abres un proyecto."
|
||||
},
|
||||
|
||||
@@ -93,6 +93,48 @@ export function buildInitScript(options?: {
|
||||
],
|
||||
},
|
||||
];
|
||||
const PROVIDER_SETUP_CATALOG = [
|
||||
{
|
||||
providerId: "claude",
|
||||
name: "Claude",
|
||||
category: "model",
|
||||
description: "Claude provider",
|
||||
setupMethod: "single_api_key",
|
||||
fields: [
|
||||
{
|
||||
key: "ANTHROPIC_API_KEY",
|
||||
label: "API key",
|
||||
secret: true,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
group: "default",
|
||||
showOnlyWhenInstalled: false,
|
||||
supportsInstall: false,
|
||||
supportsAuth: false,
|
||||
supportsAuthStatus: false,
|
||||
},
|
||||
{
|
||||
providerId: "openai",
|
||||
name: "OpenAI",
|
||||
category: "model",
|
||||
description: "OpenAI provider",
|
||||
setupMethod: "single_api_key",
|
||||
fields: [
|
||||
{
|
||||
key: "OPENAI_API_KEY",
|
||||
label: "API key",
|
||||
secret: true,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
group: "default",
|
||||
showOnlyWhenInstalled: false,
|
||||
supportsInstall: false,
|
||||
supportsAuth: false,
|
||||
supportsAuthStatus: false,
|
||||
},
|
||||
];
|
||||
|
||||
localStorage.setItem(
|
||||
"goose:onboarding:v1",
|
||||
@@ -243,7 +285,7 @@ export function buildInitScript(options?: {
|
||||
case "_goose/providers/list":
|
||||
return jsonRpcResult(message.id, { entries: PROVIDER_INVENTORY });
|
||||
case "_goose/providers/setup/catalog/list":
|
||||
return jsonRpcResult(message.id, { providers: [] });
|
||||
return jsonRpcResult(message.id, { providers: PROVIDER_SETUP_CATALOG });
|
||||
case "_goose/providers/inventory/refresh":
|
||||
return jsonRpcResult(message.id, { started: [], skipped: [] });
|
||||
case "_goose/defaults/read":
|
||||
|
||||
Reference in New Issue
Block a user