feat(FN-3059): align provider metadata and documentation

Merged feat(FN-3059) which aligns provider metadata and documentation across the codebase, updating README and getting-started docs plus refinements to the CustomProviderForm and ProviderIcon dashboard components.

Fusion-Task-Id: FN-3059
This commit is contained in:
Fusion
2026-05-01 17:39:47 -07:00
committed by gsxdsm
parent da19aa2634
commit 4d13ba01ea
15 changed files with 138 additions and 155 deletions

View File

@@ -2,7 +2,7 @@ import { useMemo, useState } from "react";
import type { CustomProviderConfig, CustomProviderModelInput } from "../api";
import "./CustomProviderForm.css";
// Keep in sync with BUILT_IN_PROVIDER_IDS in register-custom-provider-routes.ts
// Reserved built-in IDs (including hidden/deprecated aliases) to prevent custom-provider collisions.
export const BUILT_IN_PROVIDER_IDS = new Set<string>([
"anthropic", "claude-cli", "pi-claude-cli", "openai", "openai-codex", "google", "gemini", "google-antigravity",
"antigravity", "google-vertex", "vertex", "google-cloud-code", "cloud-code", "google-gemini-cli", "google-generative-ai",

View File

@@ -26,6 +26,7 @@ import { DroidCliProviderCard } from "./DroidCliProviderCard";
import { LoginInstructions } from "./LoginInstructions";
import { CustomProviderForm } from "./CustomProviderForm";
import { appendTokenQuery } from "../auth";
import { filterVisibleOnboardingAndSettingsProviders } from "./providerVisibility";
const mapLegacyCustomProviderToConfig = (
provider: CustomProvider | CustomProviderConfig,
@@ -736,12 +737,13 @@ export function ModelOnboardingModal({
const loadAuthStatus = useCallback(async () => {
try {
const { providers, ghCli } = await fetchAuthStatus();
setAuthProviders(providers);
const visibleProviders = filterVisibleOnboardingAndSettingsProviders(providers);
setAuthProviders(visibleProviders);
setGhCliStatus(ghCli);
setLoginInstructions((prev) => {
const next: Record<string, string> = {};
for (const [providerId, instructions] of Object.entries(prev)) {
const provider = providers.find((candidate) => candidate.id === providerId);
const provider = visibleProviders.find((candidate) => candidate.id === providerId);
if (provider && !provider.authenticated && provider.loginInProgress) {
next[providerId] = instructions;
}
@@ -755,7 +757,7 @@ export function ModelOnboardingModal({
if (outcome !== "pending") {
continue;
}
const provider = providers.find((candidate) => candidate.id === providerId);
const provider = visibleProviders.find((candidate) => candidate.id === providerId);
if (!provider?.loginInProgress) {
delete next[providerId];
changed = true;
@@ -766,7 +768,7 @@ export function ModelOnboardingModal({
// Remove from skippedProviders when a provider becomes authenticated
setSkippedProviders((prev) => {
const updated = { ...prev };
for (const p of providers) {
for (const p of visibleProviders) {
if (p.authenticated && updated[p.id]) {
delete updated[p.id];
}
@@ -1129,9 +1131,10 @@ export function ModelOnboardingModal({
try {
const { providers, ghCli } = await fetchAuthStatus();
setAuthProviders(providers);
const visibleProviders = filterVisibleOnboardingAndSettingsProviders(providers);
setAuthProviders(visibleProviders);
setGhCliStatus(ghCli);
const provider = providers.find((p) => p.id === providerId);
const provider = visibleProviders.find((p) => p.id === providerId);
if (provider?.authenticated) {
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);

View File

@@ -659,12 +659,14 @@ const providerConfig: Record<
google: { component: GeminiIcon, color: "var(--provider-gemini)" },
gemini: { component: GeminiIcon, color: "var(--provider-gemini)" }, // Gemini alias family
// Deprecated upstream in pi-coding-agent 0.71+, retained for legacy usage/auth history rendering.
"google-antigravity": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Gemini" },
antigravity: { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Gemini" },
"google-vertex": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Vertex AI" },
vertex: { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Vertex AI" },
"google-cloud-code": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Cloud Code" },
"cloud-code": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Cloud Code" },
// Deprecated upstream in pi-coding-agent 0.71+, retained for legacy usage/auth history rendering.
"google-gemini-cli": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Gemini CLI" },
"google-generative-ai": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Generative AI" },

View File

@@ -41,6 +41,7 @@ import { appendTokenQuery } from "../auth";
import { useConfirm } from "../hooks/useConfirm";
import { useNodes } from "../hooks/useNodes";
import { NodeHealthDot } from "./NodeHealthDot";
import { filterVisibleOnboardingAndSettingsProviders } from "./providerVisibility";
// ---------------------------------------------------------------------------
// GitHub star count — fetched once per session, cached in localStorage (1 h).
@@ -654,11 +655,12 @@ export function SettingsModal({
const loadAuthStatus = useCallback(async () => {
try {
const { providers } = await fetchAuthStatus();
setAuthProviders(providers);
const visibleProviders = filterVisibleOnboardingAndSettingsProviders(providers);
setAuthProviders(visibleProviders);
setLoginInstructions((prev) => {
const next: Record<string, string> = {};
for (const [providerId, instructions] of Object.entries(prev)) {
const provider = providers.find((candidate) => candidate.id === providerId);
const provider = visibleProviders.find((candidate) => candidate.id === providerId);
if (provider && !provider.authenticated) {
next[providerId] = instructions;
}
@@ -910,8 +912,9 @@ export function SettingsModal({
pollIntervalRef.current = setInterval(async () => {
try {
const { providers } = await fetchAuthStatus();
setAuthProviders(providers);
const provider = providers.find((p) => p.id === providerId);
const visibleProviders = filterVisibleOnboardingAndSettingsProviders(providers);
setAuthProviders(visibleProviders);
const provider = visibleProviders.find((p) => p.id === providerId);
if (provider?.authenticated) {
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);

View File

@@ -249,6 +249,26 @@ describe("ModelOnboardingModal", () => {
});
});
it("hides deprecated google CLI and antigravity providers while keeping supported Google/Gemini entries", async () => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [
{ id: "google", name: "Google", authenticated: false, type: "api_key" },
{ id: "gemini", name: "Gemini", authenticated: false, type: "api_key" },
{ id: "google-antigravity", name: "Google Antigravity", authenticated: false, type: "oauth" },
{ id: "antigravity", name: "Antigravity", authenticated: false, type: "oauth" },
{ id: "google-gemini-cli", name: "Google Gemini CLI", authenticated: false, type: "cli" },
],
});
render(<ModelOnboardingModal onComplete={vi.fn()} addToast={vi.fn()} projectId="proj_123" />);
expect(await screen.findByTestId("onboarding-provider-card-google")).toBeInTheDocument();
expect(screen.getByTestId("onboarding-provider-card-gemini")).toBeInTheDocument();
expect(screen.queryByTestId("onboarding-provider-card-google-antigravity")).not.toBeInTheDocument();
expect(screen.queryByTestId("onboarding-provider-card-antigravity")).not.toBeInTheDocument();
expect(screen.queryByText("Google Gemini CLI")).not.toBeInTheDocument();
});
it("shows Back and Next buttons on middle steps", async () => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [

View File

@@ -827,6 +827,27 @@ describe("SettingsModal", () => {
expect(screen.getByTestId("auth-status-openai")).toHaveTextContent("✗ Not connected");
});
it("hides deprecated Google CLI and antigravity auth providers", async () => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [
{ id: "google", name: "Google", authenticated: false, type: "api_key" },
{ id: "gemini", name: "Gemini", authenticated: false, type: "api_key" },
{ id: "google-antigravity", name: "Google Antigravity", authenticated: false, type: "oauth" },
{ id: "antigravity", name: "Antigravity", authenticated: false, type: "oauth" },
{ id: "google-gemini-cli", name: "Google Gemini CLI", authenticated: false, type: "cli" },
],
});
renderModal();
await waitForSettingsModalReady();
expect(screen.getByTestId("auth-provider-icon-google")).toBeInTheDocument();
expect(screen.getByTestId("auth-provider-icon-gemini")).toBeInTheDocument();
expect(screen.queryByTestId("auth-provider-icon-google-antigravity")).not.toBeInTheDocument();
expect(screen.queryByTestId("auth-provider-icon-antigravity")).not.toBeInTheDocument();
expect(screen.queryByText("Google Gemini CLI")).not.toBeInTheDocument();
});
it("scrolls settings content to top after OAuth login succeeds", async () => {
const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);
mockLoginProvider.mockResolvedValue({ url: "https://example.com/auth", instructions: "" });

View File

@@ -0,0 +1,17 @@
const HIDDEN_ONBOARDING_AND_SETTINGS_PROVIDER_IDS = new Set([
"google-antigravity",
"antigravity",
"google-gemini-cli",
]);
export function isProviderVisibleInOnboardingAndSettings(providerId: string): boolean {
return !HIDDEN_ONBOARDING_AND_SETTINGS_PROVIDER_IDS.has(providerId);
}
export function filterVisibleOnboardingAndSettingsProviders<T extends { id: string }>(
providers: T[],
): T[] {
return providers.filter((provider) => isProviderVisibleInOnboardingAndSettings(provider.id));
}
export { HIDDEN_ONBOARDING_AND_SETTINGS_PROVIDER_IDS };