From 7ffea9f4a661ad2b76ea501ca9c8342dbdd968a6 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 12 Jun 2026 07:13:00 -0700 Subject: [PATCH] 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 --- .../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(-) create mode 100644 .changeset/fn-6290-expose-google-generative-ai.md diff --git a/.changeset/fn-6290-expose-google-generative-ai.md b/.changeset/fn-6290-expose-google-generative-ai.md new file mode 100644 index 0000000000..456e32687a --- /dev/null +++ b/.changeset/fn-6290-expose-google-generative-ai.md @@ -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. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index ee075e3c5d..a7506e9171 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -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: diff --git a/docs/settings-reference.md b/docs/settings-reference.md index a8c0826ea6..ca24be258b 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -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[]` | `[]` | 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[]` | `[]` | 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. | diff --git a/packages/dashboard/app/components/CustomProvidersSection.tsx b/packages/dashboard/app/components/CustomProvidersSection.tsx index 1bc976e567..0f2bc0483a 100644 --- a/packages/dashboard/app/components/CustomProvidersSection.tsx +++ b/packages/dashboard/app/components/CustomProvidersSection.tsx @@ -349,6 +349,7 @@ export function CustomProvidersSection({ embedded = false, onProviderChange }: C + @@ -468,6 +469,7 @@ export function CustomProvidersSection({ embedded = false, onProviderChange }: C + diff --git a/packages/dashboard/app/components/__tests__/CustomProvidersSection.test.tsx b/packages/dashboard/app/components/__tests__/CustomProvidersSection.test.tsx index e41bdb430b..0d4330fe8a 100644 --- a/packages/dashboard/app/components/__tests__/CustomProvidersSection.test.tsx +++ b/packages/dashboard/app/components/__tests__/CustomProvidersSection.test.tsx @@ -145,6 +145,68 @@ describe("CustomProvidersSection", () => { }); }); + it("exposes Google Generative AI in the add provider API type dropdown", async () => { + mockFetchCustomProviders.mockResolvedValueOnce([]); + + render(); + + 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(); + + 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(); + + 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(); diff --git a/packages/i18n/locales/en/app.json b/packages/i18n/locales/en/app.json index 0e13bfb021..46d83967d6 100644 --- a/packages/i18n/locales/en/app.json +++ b/packages/i18n/locales/en/app.json @@ -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", diff --git a/packages/i18n/locales/es/app.json b/packages/i18n/locales/es/app.json index 0e25bd2c8b..70cb8217df 100644 --- a/packages/i18n/locales/es/app.json +++ b/packages/i18n/locales/es/app.json @@ -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", diff --git a/packages/i18n/locales/fr/app.json b/packages/i18n/locales/fr/app.json index 86c4aef2a3..ec222a4a0c 100644 --- a/packages/i18n/locales/fr/app.json +++ b/packages/i18n/locales/fr/app.json @@ -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", diff --git a/packages/i18n/locales/ko/app.json b/packages/i18n/locales/ko/app.json index 8dfa242996..3073a36b9f 100644 --- a/packages/i18n/locales/ko/app.json +++ b/packages/i18n/locales/ko/app.json @@ -4300,6 +4300,7 @@ "addCustom": "사용자 정의 공급자 추가", "apiKeyLabel": "API 키", "apiTypeAnthropic": "Anthropic 호환", + "apiTypeGoogle": "Google Generative AI", "apiTypeInvalid": "API 유형이 유효하지 않습니다.", "apiTypeLabel": "API 유형", "apiTypeOpenAi": "OpenAI 호환", diff --git a/packages/i18n/locales/zh-CN/app.json b/packages/i18n/locales/zh-CN/app.json index 0b77256cc2..4b65be97bd 100644 --- a/packages/i18n/locales/zh-CN/app.json +++ b/packages/i18n/locales/zh-CN/app.json @@ -4300,6 +4300,7 @@ "addCustom": "添加自定义提供程序", "apiKeyLabel": "API 密钥", "apiTypeAnthropic": "Anthropic 兼容", + "apiTypeGoogle": "Google Generative AI", "apiTypeInvalid": "API 类型无效。", "apiTypeLabel": "API 类型", "apiTypeOpenAi": "OpenAI 兼容", diff --git a/packages/i18n/locales/zh-TW/app.json b/packages/i18n/locales/zh-TW/app.json index 419267b7bd..465d7ecc97 100644 --- a/packages/i18n/locales/zh-TW/app.json +++ b/packages/i18n/locales/zh-TW/app.json @@ -4300,6 +4300,7 @@ "addCustom": "添加自訂提供者", "apiKeyLabel": "API 密鑰", "apiTypeAnthropic": "Anthropic 相容", + "apiTypeGoogle": "Google Generative AI", "apiTypeInvalid": "API 類型無效。", "apiTypeLabel": "API 類型", "apiTypeOpenAi": "OpenAI 相容",