FN-6290: expose Google Generative AI custom providers

Expose Google Generative AI as a selectable custom-provider API type across settings UI and docs.

- Add Google Generative AI options to custom-provider add and edit dropdowns.
- Cover add, edit, and existing Google-provider selection behavior with dashboard tests.
- Document the supported API type and add localized provider labels.
- Add a changeset for the published Fusion package.

Files changed:
 .changeset/fn-6290-expose-google-generative-ai.md  |  5 ++
 docs/dashboard-guide.md                            |  5 +-
 docs/settings-reference.md                         |  2 +-
 .../app/components/CustomProvidersSection.tsx      |  2 +
 .../__tests__/CustomProvidersSection.test.tsx      | 62 ++++++++++++++++++++++
 packages/i18n/locales/en/app.json                  |  1 +
 packages/i18n/locales/es/app.json                  |  1 +
 packages/i18n/locales/fr/app.json                  |  1 +
 packages/i18n/locales/ko/app.json                  |  1 +
 packages/i18n/locales/zh-CN/app.json               |  1 +
 packages/i18n/locales/zh-TW/app.json               |  1 +
 11 files changed, 79 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-6290

Fusion-Task-Lineage: 295cbba2-cae1-4998-9456-d6a306be6e34
This commit is contained in:
gsxdsm
2026-06-12 07:13:00 -07:00
parent 2085610e9e
commit 7ffea9f4a6
11 changed files with 79 additions and 3 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": minor
---
Expose Google Generative AI as a selectable custom-provider API type in the dashboard settings UI and documentation.

View File

@@ -119,13 +119,14 @@ Behavior:
## Custom Providers
Custom Providers live in **Settings → Authentication → Custom Providers**, inside the **Advanced: Custom Providers** disclosure. Use this section to add user-defined model providers that speak an OpenAI-compatible API, the OpenAI Responses API, or an Anthropic-compatible API. After a provider is saved with models, those models become selectable in model dropdowns, including **Settings → Project Models** lanes and workflow model lanes.
Custom Providers live in **Settings → Authentication → Custom Providers**, inside the **Advanced: Custom Providers** disclosure. Use this section to add user-defined model providers that speak an OpenAI-compatible API, the OpenAI Responses API, an Anthropic-compatible API, or Google Generative AI. After a provider is saved with models, those models become selectable in model dropdowns, including **Settings → Project Models** lanes and workflow model lanes.
Supported **API type** values match the dropdown in the form:
- **OpenAI-compatible**
- **OpenAI Responses**
- **Anthropic-compatible**
- **Google Generative AI**
The custom-provider form uses these fields:
@@ -143,7 +144,7 @@ Use **Detect Models** to auto-fill **Available models** from the provider's `/mo
2. Expand **Advanced: Custom Providers** if it is collapsed.
3. Select **Add Custom Provider**.
4. Enter a **Provider name**.
5. Choose the correct **API type**: **OpenAI-compatible**, **OpenAI Responses**, or **Anthropic-compatible**.
5. Choose the correct **API type**: **OpenAI-compatible**, **OpenAI Responses**, **Anthropic-compatible**, or **Google Generative AI**.
6. Enter the provider **Base URL**. The value must be a valid `http` or `https` URL.
7. If the provider requires authentication, enter its **API key**.
8. Populate **Available models** by either:

View File

@@ -58,7 +58,7 @@ Fusion automatically falls back to ntfy's JSON publish format when a notificatio
| `webhookFormat` | `"slack" \| "discord" \| "generic"` | `"generic"` | Webhook payload format. Part of legacy flat settings. |
| `webhookEvents` | `string[]` | `[]` | Event filter for webhook notifications. Empty/omitted means all events. Part of legacy flat settings. |
| `notificationProviders` | `NotificationProviderConfig[]` | `[]` | Array of pluggable notification provider configurations. Each entry uses `{ id, name, enabled, config }` and is dispatched by provider ID (for example `ntfy` or `webhook`). |
| `customProviders` | `CustomProvider[]` | `[]` | <a id="customproviders"></a>User-defined OpenAI-compatible, OpenAI Responses API (`apiType: "openai-responses"`), or Anthropic-compatible providers used by the custom-provider API (`/api/custom-providers`). Each entry uses `{ id, name, apiType, baseUrl, apiKey?, supportsDeveloperRole?, models? }`; `supportsDeveloperRole` is an OpenAI-compatible opt-in that enables `developer` role emission (default/omitted is `false`, forcing safe `system` role). API keys are stored raw but masked in API responses. Fusion resolves these providers from the active global settings directory (`~/.fusion`, with legacy `~/.pi/fusion` and `~/.pi/kb` migration support) so custom-provider models remain available after restart. |
| `customProviders` | `CustomProvider[]` | `[]` | <a id="customproviders"></a>User-defined OpenAI-compatible, OpenAI Responses API (`apiType: "openai-responses"`), Anthropic-compatible, or Google Generative AI (`apiType: "google-generative-ai"`) providers used by the custom-provider API (`/api/custom-providers`). Each entry uses `{ id, name, apiType, baseUrl, apiKey?, supportsDeveloperRole?, models? }`; `supportsDeveloperRole` is an OpenAI-compatible opt-in that enables `developer` role emission (default/omitted is `false`, forcing safe `system` role). API keys are stored raw but masked in API responses. Fusion resolves these providers from the active global settings directory (`~/.fusion`, with legacy `~/.pi/fusion` and `~/.pi/kb` migration support) so custom-provider models remain available after restart. |
| `defaultProjectId` | `string` | `undefined` | Default project for multi-project CLI operations when `--project` is omitted. |
| `setupComplete` | `boolean` | `undefined` | Tracks completion of first-run setup. |
| `favoriteProviders` | `string[]` | `undefined` | Pinned providers shown first in model selectors. |

View File

@@ -349,6 +349,7 @@ export function CustomProvidersSection({ embedded = false, onProviderChange }: C
<option value="openai-compatible">{t("providers.apiTypeOpenAi", "OpenAI-compatible")}</option>
<option value="openai-responses">{t("providers.apiTypeOpenAiResp", "OpenAI Responses")}</option>
<option value="anthropic-compatible">{t("providers.apiTypeAnthropic", "Anthropic-compatible")}</option>
<option value="google-generative-ai">{t("providers.apiTypeGoogle", "Google Generative AI")}</option>
</select>
</div>
@@ -468,6 +469,7 @@ export function CustomProvidersSection({ embedded = false, onProviderChange }: C
<option value="openai-compatible">{t("providers.apiTypeOpenAi", "OpenAI-compatible")}</option>
<option value="openai-responses">{t("providers.apiTypeOpenAiResp", "OpenAI Responses")}</option>
<option value="anthropic-compatible">{t("providers.apiTypeAnthropic", "Anthropic-compatible")}</option>
<option value="google-generative-ai">{t("providers.apiTypeGoogle", "Google Generative AI")}</option>
</select>
</div>

View File

@@ -145,6 +145,68 @@ describe("CustomProvidersSection", () => {
});
});
it("exposes Google Generative AI in the add provider API type dropdown", async () => {
mockFetchCustomProviders.mockResolvedValueOnce([]);
render(<CustomProvidersSection embedded />);
await waitFor(() => {
expect(screen.getByRole("button", { name: /Add Custom Provider/i })).toBeTruthy();
});
fireEvent.click(screen.getByRole("button", { name: /Add Custom Provider/i }));
const apiTypeSelect = screen.getByLabelText("API type") as HTMLSelectElement;
expect(Array.from(apiTypeSelect.options).map((option) => option.value)).toContain("google-generative-ai");
expect(screen.getByRole("option", { name: "Google Generative AI" })).toBeTruthy();
});
it("exposes Google Generative AI in the edit provider API type dropdown", async () => {
mockFetchCustomProviders.mockResolvedValueOnce([
{
id: "test-id",
name: "Editable Provider",
apiType: "anthropic-compatible",
baseUrl: "https://api.example.com",
},
]);
render(<CustomProvidersSection embedded />);
await waitFor(() => {
expect(screen.getByLabelText("Edit Editable Provider")).toBeTruthy();
});
fireEvent.click(screen.getByLabelText("Edit Editable Provider"));
const apiTypeSelect = screen.getByLabelText("API type") as HTMLSelectElement;
expect(Array.from(apiTypeSelect.options).map((option) => option.value)).toContain("google-generative-ai");
expect(screen.getByRole("option", { name: "Google Generative AI" })).toBeTruthy();
});
it("selects Google Generative AI when editing an existing Google provider", async () => {
mockFetchCustomProviders.mockResolvedValueOnce([
{
id: "google-id",
name: "Google Provider",
apiType: "google-generative-ai",
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
},
]);
render(<CustomProvidersSection embedded />);
await waitFor(() => {
expect(screen.getByLabelText("Edit Google Provider")).toBeTruthy();
});
fireEvent.click(screen.getByLabelText("Edit Google Provider"));
const apiTypeSelect = screen.getByLabelText("API type") as HTMLSelectElement;
expect(apiTypeSelect.value).toBe("google-generative-ai");
expect(apiTypeSelect.selectedOptions[0]?.value).toBe("google-generative-ai");
});
it("shows validation errors for empty name and invalid baseUrl", async () => {
render(<CustomProvidersSection embedded />);

View File

@@ -4302,6 +4302,7 @@
"apiKeyKeepPlaceholder": "Leave blank to keep current key",
"apiKeyLabel": "API key",
"apiTypeAnthropic": "Anthropic-compatible",
"apiTypeGoogle": "Google Generative AI",
"apiTypeInvalid": "API type is invalid.",
"apiTypeLabel": "API type",
"apiTypeOpenAi": "OpenAI-compatible",

View File

@@ -4300,6 +4300,7 @@
"addCustom": "Agregar proveedor personalizado",
"apiKeyLabel": "Clave de API",
"apiTypeAnthropic": "Compatible con Anthropic",
"apiTypeGoogle": "Google Generative AI",
"apiTypeInvalid": "El tipo de API no es válido.",
"apiTypeLabel": "Tipo de API",
"apiTypeOpenAi": "Compatible con OpenAI",

View File

@@ -4300,6 +4300,7 @@
"addCustom": "Ajouter un fournisseur personnalisé",
"apiKeyLabel": "Clé API",
"apiTypeAnthropic": "Compatible avec Anthropic",
"apiTypeGoogle": "Google Generative AI",
"apiTypeInvalid": "Le type d'API est invalide.",
"apiTypeLabel": "Type d'API",
"apiTypeOpenAi": "Compatible avec OpenAI",

View File

@@ -4300,6 +4300,7 @@
"addCustom": "사용자 정의 공급자 추가",
"apiKeyLabel": "API 키",
"apiTypeAnthropic": "Anthropic 호환",
"apiTypeGoogle": "Google Generative AI",
"apiTypeInvalid": "API 유형이 유효하지 않습니다.",
"apiTypeLabel": "API 유형",
"apiTypeOpenAi": "OpenAI 호환",

View File

@@ -4300,6 +4300,7 @@
"addCustom": "添加自定义提供程序",
"apiKeyLabel": "API 密钥",
"apiTypeAnthropic": "Anthropic 兼容",
"apiTypeGoogle": "Google Generative AI",
"apiTypeInvalid": "API 类型无效。",
"apiTypeLabel": "API 类型",
"apiTypeOpenAi": "OpenAI 兼容",

View File

@@ -4300,6 +4300,7 @@
"addCustom": "添加自訂提供者",
"apiKeyLabel": "API 密鑰",
"apiTypeAnthropic": "Anthropic 相容",
"apiTypeGoogle": "Google Generative AI",
"apiTypeInvalid": "API 類型無效。",
"apiTypeLabel": "API 類型",
"apiTypeOpenAi": "OpenAI 相容",