goose2 distribution bundling (#8911)

This commit is contained in:
Jack Amadeo
2026-05-05 11:20:49 -04:00
committed by GitHub
parent fbb5e3685d
commit 9f3fe88afa
25 changed files with 589 additions and 29 deletions
+33 -1
View File
@@ -6,6 +6,9 @@ import { discoverAcpProvidersFromEntries } from "@/shared/api/acp";
import { setNotificationHandler, getClient } from "@/shared/api/acpConnection";
import notificationHandler from "@/shared/api/acpNotificationHandler";
import { perfLog } from "@/shared/lib/perfLog";
import { parseProviderAllowlist } from "@/features/providers/distroProviderConstraints";
import { getModelProviders } from "@/features/providers/providerCatalog";
import { useDistroStore } from "@/features/settings/stores/distroStore";
export function useAppStartup() {
useEffect(() => {
@@ -25,6 +28,18 @@ export function useAppStartup() {
const store = useAgentStore.getState();
const inventoryStore = useProviderInventoryStore.getState();
const distroStore = useDistroStore.getState();
const loadDistroBundle = async () => {
try {
const { getDistroBundle } = await import("@/shared/api/distro");
const manifest = await getDistroBundle();
distroStore.setManifest(manifest);
} catch (err) {
console.error("Failed to load distro bundle on startup:", err);
distroStore.setManifest({ present: false });
}
};
const loadPersonas = async () => {
const t0 = performance.now();
store.setPersonasLoading(true);
@@ -57,7 +72,22 @@ export function useAppStartup() {
// Derive ACP providers from the same response
const providers = discoverAcpProvidersFromEntries(entries);
store.setProviders(providers);
const providerAllowlist = parseProviderAllowlist(
useDistroStore.getState().manifest,
);
if (!providerAllowlist) {
store.setProviders(providers);
} else {
const hasAllowedModelProvider = getModelProviders().some(
(provider) => providerAllowlist.has(provider.id),
);
store.setProviders(
providers.filter(
(provider) =>
provider.id !== "goose" || hasAllowedModelProvider,
),
);
}
perfLog(
`[perf:startup] loadProvidersAndInventory done in ${(performance.now() - t0).toFixed(1)}ms (entries=${entries.length}, providers=${providers.length})`,
@@ -87,6 +117,8 @@ export function useAppStartup() {
setActiveSession(null);
};
await loadDistroBundle();
const providersAndInventoryLoad = loadProvidersAndInventory();
await Promise.allSettled([
@@ -0,0 +1,63 @@
import { describe, expect, it } from "vitest";
import { filterModelProvidersForDistro } from "./distroProviderConstraints";
describe("filterModelProvidersForDistro", () => {
const providers = [
{
id: "anthropic",
displayName: "Anthropic",
category: "model",
description: "Claude models",
setupMethod: "single_api_key",
tier: "promoted",
},
{
id: "openai",
displayName: "OpenAI",
category: "model",
description: "GPT models",
setupMethod: "single_api_key",
tier: "promoted",
},
{
id: "ollama",
displayName: "Ollama",
category: "model",
description: "Local models",
setupMethod: "local",
tier: "promoted",
},
] as const;
it("returns all providers when no distro is present", () => {
expect(
filterModelProvidersForDistro([...providers], { present: false }),
).toEqual(providers);
});
it("returns all providers when no allowlist is configured", () => {
expect(
filterModelProvidersForDistro([...providers], {
present: true,
}),
).toEqual(providers);
});
it("filters providers to the configured allowlist", () => {
expect(
filterModelProvidersForDistro([...providers], {
present: true,
providerAllowlist: "openai, ollama",
}),
).toEqual([providers[1], providers[2]]);
});
it("ignores whitespace and empty allowlist items", () => {
expect(
filterModelProvidersForDistro([...providers], {
present: true,
providerAllowlist: " anthropic ,, openai ",
}),
).toEqual([providers[0], providers[1]]);
});
});
@@ -0,0 +1,34 @@
import type { ProviderCatalogEntry } from "@/shared/types/providers";
import type { DistroBundleInfo } from "@/shared/types/distro";
export function parseProviderAllowlist(
distro: DistroBundleInfo | null | undefined,
): Set<string> | null {
if (!distro?.present) {
return null;
}
const raw = distro.providerAllowlist?.trim();
if (!raw) {
return null;
}
const providerIds = raw
.split(",")
.map((providerId) => providerId.trim())
.filter(Boolean);
return providerIds.length > 0 ? new Set(providerIds) : null;
}
export function filterModelProvidersForDistro(
providers: ProviderCatalogEntry[],
distro: DistroBundleInfo | null | undefined,
): ProviderCatalogEntry[] {
const allowlist = parseProviderAllowlist(distro);
if (!allowlist) {
return providers;
}
return providers.filter((provider) => allowlist.has(provider.id));
}
@@ -6,27 +6,22 @@ import type {
ProviderInventoryModelDto,
} from "@aaif/goose-sdk";
import { getModelProviders } from "../providerCatalog";
const MODEL_PROVIDER_IDS = new Set(getModelProviders().map((p) => p.id));
import { useDistroStore } from "@/features/settings/stores/distroStore";
import { filterModelProvidersForDistro } from "../distroProviderConstraints";
function isConfiguredGooseModelProvider(
entry: ProviderInventoryEntryDto,
modelProviderIds: Set<string>,
): boolean {
if (!entry.configured) {
return false;
}
const isCuratedModelProvider = MODEL_PROVIDER_IDS.has(entry.providerId);
if (entry.providerType === "Custom") {
return entry.providerId.startsWith("custom_");
}
if (entry.providerType === "Declarative") {
return isCuratedModelProvider;
}
return isCuratedModelProvider;
return modelProviderIds.has(entry.providerId);
}
function inventoryModelToOption(
@@ -48,6 +43,7 @@ function inventoryModelToOption(
export function useProviderInventory() {
const entries = useProviderInventoryStore((s) => s.entries);
const loading = useProviderInventoryStore((s) => s.loading);
const distro = useDistroStore((s) => s.manifest);
const getEntry = useCallback(
(providerId: string) => entries.get(providerId),
@@ -63,9 +59,22 @@ export function useProviderInventory() {
[entries],
);
const modelProviderIds = useMemo(
() =>
new Set(
filterModelProvidersForDistro(getModelProviders(), distro).map(
(provider) => provider.id,
),
),
[distro],
);
const configuredModelProviderEntries = useMemo(
() => [...entries.values()].filter(isConfiguredGooseModelProvider),
[entries],
() =>
[...entries.values()].filter((entry) =>
isConfiguredGooseModelProvider(entry, modelProviderIds),
),
[entries, modelProviderIds],
);
const getModelsForAgent = useCallback(
@@ -84,8 +93,8 @@ export function useProviderInventory() {
const configuredProviderIds = useMemo(
() =>
[...entries.values()]
.filter((e) => e.configured)
.map((e) => e.providerId),
.filter((entry) => entry.configured)
.map((entry) => entry.providerId),
[entries],
);
@@ -0,0 +1,18 @@
import { create } from "zustand";
import type { DistroBundleInfo } from "@/shared/types/distro";
interface DistroState {
loaded: boolean;
manifest: DistroBundleInfo;
setManifest: (manifest: DistroBundleInfo) => void;
}
const EMPTY_DISTRO: DistroBundleInfo = {
present: false,
};
export const useDistroStore = create<DistroState>((set) => ({
loaded: false,
manifest: EMPTY_DISTRO,
setManifest: (manifest) => set({ manifest, loaded: true }),
}));
@@ -19,6 +19,8 @@ import {
getModelProviders,
} from "@/features/providers/providerCatalog";
import { useCredentials } from "@/features/providers/hooks/useCredentials";
import { useDistroStore } from "@/features/settings/stores/distroStore";
import { filterModelProvidersForDistro } from "@/features/providers/distroProviderConstraints";
import { useCustomProviders } from "@/features/providers/hooks/useCustomProviders";
import {
CustomProviderChoice,
@@ -100,6 +102,7 @@ interface PendingCustomProviderDelete {
export function ProvidersSettings() {
const { t } = useTranslation(["settings", "common"]);
const distro = useDistroStore((state) => state.manifest);
const [showAllModels, setShowAllModels] = useState(false);
const [modelOrder, setModelOrder] = useState<string[] | null>(null);
const [customDialogOpen, setCustomDialogOpen] = useState(false);
@@ -137,8 +140,12 @@ export function ProvidersSettings() {
);
const allModels = useMemo(
() => toDisplayInfo(getModelProviders(), configuredIds),
[configuredIds],
() =>
toDisplayInfo(
filterModelProvidersForDistro(getModelProviders(), distro),
configuredIds,
),
[configuredIds, distro],
);
const sortedModels = useMemo(() => {
+6
View File
@@ -0,0 +1,6 @@
import { invoke } from "@tauri-apps/api/core";
import type { DistroBundleInfo } from "@/shared/types/distro";
export async function getDistroBundle(): Promise<DistroBundleInfo> {
return invoke("get_distro_bundle");
}
+1
View File
@@ -1,4 +1,5 @@
export * from "./agents";
export * from "./acp";
export * from "./distro";
export * from "./git";
export * from "./pathResolver";
+7
View File
@@ -0,0 +1,7 @@
export interface DistroBundleInfo {
present: boolean;
appVersion?: string;
featureToggles?: Record<string, boolean>;
extensionAllowlist?: string;
providerAllowlist?: string;
}
+1
View File
@@ -1,3 +1,4 @@
export * from "./distro";
export * from "./messages";
export * from "./agents";
export * from "./chat";
@@ -12,6 +12,7 @@ import type { ComponentProps } from "react";
import { createContext, useContext, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { getUsage } from "tokenlens";
import { useDistroStore } from "@/features/settings/stores/distroStore";
const PERCENT_MAX = 100;
const ICON_RADIUS = 10;
@@ -61,6 +62,13 @@ export const Context = ({
);
};
function useCostTrackingEnabled() {
const featureToggles = useDistroStore(
(state) => state.manifest.featureToggles,
);
return featureToggles?.costTracking !== false;
}
const ContextIcon = () => {
const { t } = useTranslation("common");
const { usedTokens, maxTokens } = useContextValue();
@@ -199,6 +207,7 @@ export const ContextContentFooter = ({
className,
...props
}: ContextContentFooterProps) => {
const costTrackingEnabled = useCostTrackingEnabled();
const { t } = useTranslation("common");
const { formatNumber } = useLocaleFormatting();
const { modelId, usage } = useContextValue();
@@ -216,6 +225,10 @@ export const ContextContentFooter = ({
style: "currency",
});
if (!costTrackingEnabled) {
return null;
}
return (
<div
className={cn(
@@ -241,6 +254,7 @@ const TokensWithCost = ({
tokens?: number;
costText?: string;
}) => {
const costTrackingEnabled = useCostTrackingEnabled();
const { formatNumber } = useLocaleFormatting();
return (
@@ -250,7 +264,7 @@ const TokensWithCost = ({
: formatNumber(tokens, {
notation: "compact",
})}
{costText ? (
{costTrackingEnabled && costText ? (
<span className="ml-2 text-muted-foreground"> {costText}</span>
) : null}
</span>