Skip to content

feat: add provider configuration export/import#1146

Open
NJX-njx wants to merge 1 commit intomoeru-ai:mainfrom
NJX-njx:NJX-njx/feat/provider-config-export-import
Open

feat: add provider configuration export/import#1146
NJX-njx wants to merge 1 commit intomoeru-ai:mainfrom
NJX-njx:NJX-njx/feat/provider-config-export-import

Conversation

@NJX-njx
Copy link
Contributor

@NJX-njx NJX-njx commented Mar 5, 2026

Summary

Adds the ability to export and import provider configurations (API keys, endpoints, settings) from the Data settings page. This addresses Issue #973.

Changes

  • **\packages/stage-ui/src/composables/use-data-maintenance.ts**: Added \exportProviderConfigurations()\ and \importProviderConfigurations()\ functions with format validation (\provider-configurations:v1)
  • **\packages/stage-pages/src/pages/settings/data/index.vue**: Added a new provider configurations card section with Export and Import buttons, following the established chat sessions pattern
  • **\packages/i18n/src/locales/en/settings.yaml**: Added i18n keys for export/import labels and status messages

How it works

  • Export: Creates a JSON blob containing all provider credentials and added-provider flags, triggering a file download
  • Import: Opens a file picker, validates the JSON format, and merges provider configurations into the current state (marks imported providers as added)
  • Export format includes a version identifier (\provider-configurations:v1) for future compatibility

Testing

  • Verified no TypeScript errors
  • UI follows the established pattern from chat sessions export/import

Closes #973

- Add exportProviderConfigurations() and importProviderConfigurations() to useDataMaintenance composable
- Add provider export/import UI card section in data settings page
- Add i18n keys for provider export/import labels and status messages
- Export format: provider-configurations:v1 with providers and addedProviders data
- Import validates format and merges provider configs

Closes moeru-ai#973
Copilot AI review requested due to automatic review settings March 5, 2026 14:59
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances data management capabilities by introducing features for exporting and importing provider configurations. Users can now easily back up their provider settings or transfer them between instances, improving data portability and simplifying the setup of various AI providers. The changes provide a robust and user-friendly mechanism for managing sensitive configuration data through the application's settings.

Highlights

  • Provider Configuration Export: Added the ability to export all configured provider settings, including API keys and endpoints, into a versioned JSON file.
  • Provider Configuration Import: Implemented functionality to import provider configurations from a JSON file, merging them into the application's current state and marking imported providers as added.
  • User Interface Integration: Introduced a new dedicated section on the Data settings page with 'Export' and 'Import' buttons for provider configurations, mirroring the existing chat sessions pattern.
  • Format Validation: Included a version identifier ('provider-configurations:v1') and validation logic to ensure the integrity and compatibility of imported provider configuration files.
Changelog
  • packages/stage-pages/src/pages/settings/data/index.vue
    • Integrated exportProviderConfigurations and importProviderConfigurations from the useDataMaintenance composable.
    • Added a new providerImportFileInput ref to handle file selection for imports.
    • Implemented triggerProviderExport, triggerProviderImportPicker, and handleProviderImport functions to manage the UI-driven export and import workflows.
    • Introduced a new UI card section for 'Provider Configurations' on the data settings page, complete with export and import buttons.
  • packages/stage-ui/src/composables/use-data-maintenance.ts
    • Developed the exportProviderConfigurations function to serialize current provider data and added providers into a versioned JSON blob.
    • Created the importProviderConfigurations function to parse and apply imported provider data, including merging configurations and marking providers as added.
    • Added isProviderConfigPayload for robust type guarding and validation of the import payload structure.
    • Exported the newly created exportProviderConfigurations and importProviderConfigurations functions for use across the application.
Ignored Files
  • Ignored by pattern: packages/i18n/src/** (1)
    • packages/i18n/src/locales/en/settings.yaml
Activity
  • No specific activity (comments, reviews, etc.) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds export/import support for provider configurations from the Data settings page to improve developer experience when resetting local data (Issue #973).

Changes:

  • Added provider configuration export/import helpers in useDataMaintenance with a provider-configurations:v1 payload format.
  • Added a new Providers card in the Data settings page UI with Export/Import actions and a hidden file input.
  • Added English i18n strings for the new Providers export/import UI and status messages.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
packages/stage-ui/src/composables/use-data-maintenance.ts Introduces provider config export/import logic and payload format checking.
packages/stage-pages/src/pages/settings/data/index.vue Adds Providers export/import UI controls and wiring to the composable functions.
packages/i18n/src/locales/en/settings.yaml Adds new labels/status messages for provider export/import and updates the Providers description text.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to +87
function isProviderConfigPayload(payload: unknown): payload is { format: string, providers: Record<string, Record<string, unknown>>, addedProviders: Record<string, boolean> } {
if (!payload || typeof payload !== 'object')
return false
return (payload as { format?: string }).format === 'provider-configurations:v1'
&& typeof (payload as any).providers === 'object'
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isProviderConfigPayload only checks format and that providers is an object; null (and arrays) pass this check, which can then cause runtime errors in Object.entries(payload.providers) and unexpected imports. Strengthen the guard to ensure providers is a non-null plain object and validate addedProviders (or make it optional in the predicate) before iterating.

Suggested change
function isProviderConfigPayload(payload: unknown): payload is { format: string, providers: Record<string, Record<string, unknown>>, addedProviders: Record<string, boolean> } {
if (!payload || typeof payload !== 'object')
return false
return (payload as { format?: string }).format === 'provider-configurations:v1'
&& typeof (payload as any).providers === 'object'
function isProviderConfigPayload(payload: unknown): payload is { format: string, providers: Record<string, Record<string, unknown>>, addedProviders?: Record<string, boolean> } {
if (!payload || typeof payload !== 'object')
return false
const maybePayload = payload as {
format?: string
providers?: unknown
addedProviders?: unknown
}
if (maybePayload.format !== 'provider-configurations:v1')
return false
const providers = maybePayload.providers
if (!providers || typeof providers !== 'object' || Array.isArray(providers))
return false
const addedProviders = maybePayload.addedProviders
if (addedProviders !== undefined && (addedProviders === null || typeof addedProviders !== 'object' || Array.isArray(addedProviders)))
return false
return true

Copilot uses AI. Check for mistakes.
const currentProviders = providersStore.providers
for (const [id, config] of Object.entries(payload.providers)) {
if (config && typeof config === 'object') {
currentProviders[id] = config
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

importProviderConfigurations replaces each provider config object (currentProviders[id] = config). For forward compatibility (e.g., importing an older file that lacks newly added config fields), consider merging into the existing config (or defaults) rather than wholesale replacement, so missing fields don't get dropped.

Suggested change
currentProviders[id] = config
const existing = currentProviders[id]
if (existing && typeof existing === 'object') {
currentProviders[id] = {
...(existing as Record<string, unknown>),
...(config as Record<string, unknown>),
}
}
else {
currentProviders[id] = config
}

Copilot uses AI. Check for mistakes.
Comment on lines +107 to +129
function triggerProviderImportPicker() {
importError.value = ''
providerImportFileInput.value?.click()
}

function handleProviderImport(event: Event) {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file)
return

file.text().then((raw) => {
const parsed = JSON.parse(raw) as Record<string, unknown>
importProviderConfigurations(parsed)
setStatus(t('settings.pages.data.status.providers_imported'))
importError.value = ''
}).catch((error) => {
console.error(error)
importError.value = t('settings.pages.data.status.providers_import_error')
setStatus(error instanceof Error ? error.message : String(error), 'error')
}).finally(() => {
target.value = ''
})
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider import/export reuses importError, but the only place that displays importError is inside the chat sessions card. As a result, provider import failures won't be shown next to the providers section (and may appear under the chat section instead). Use a separate providerImportError state and render it under the providers card, and clear that state in triggerProviderImportPicker/handleProviderImport.

Copilot uses AI. Check for mistakes.
providers:
title: Providers
description: Reset all provider settings and credentials.
description: Export or import provider configurations and credentials.
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

settings.pages.data.sections.providers.description is used both for the new providers export/import card and for the danger-zone provider reset section (see settings page template). Updating it to an export/import description makes the danger-zone copy inaccurate. Introduce a new i18n key for the export/import card description (or a separate danger-zone description) so each section has correct text.

Suggested change
description: Export or import provider configurations and credentials.
description: Reset provider configurations and credentials.
export_import_description: Export or import provider configurations and credentials.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a valuable feature for exporting and importing provider configurations. The current implementation of the import process has security vulnerabilities related to prototype pollution and credential theft, and a critical bug in the import logic prevents configurations from being saved correctly. Additionally, there are suggestions to enhance correctness by completing a type guard and improving code consistency by refactoring to async/await. Addressing these points will ensure the feature is secure and robust.

Comment on lines +90 to +108
function importProviderConfigurations(payload: Record<string, unknown>) {
if (!isProviderConfigPayload(payload))
throw new Error('Invalid provider configuration export format')

const currentProviders = providersStore.providers
for (const [id, config] of Object.entries(payload.providers)) {
if (config && typeof config === 'object') {
currentProviders[id] = config
}
}

if (payload.addedProviders) {
for (const [id, added] of Object.entries(payload.addedProviders)) {
if (added) {
providersStore.markProviderAdded(id)
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The importProviderConfigurations function lacks proper validation of the imported JSON payload, leading to two main vulnerabilities: 1. Credential Theft: An attacker can craft a malicious JSON file that sets the baseUrl of a provider to an attacker-controlled server, potentially stealing API keys. 2. Prototype Pollution: The function iterates over the providers object in the payload and assigns its properties to the currentProviders object. If the payload contains a __proto__ key, it can lead to prototype pollution. Additionally, providersStore.providers returns a Vue Ref object. The code attempts to assign properties directly to this Ref object (currentProviders[id] = config) instead of its underlying .value, which will not update the stored data correctly. To remediate these issues, you should: - Validate that the provider IDs in the imported file are valid (exist in providerMetadata). - Explicitly check for and reject sensitive keys like __proto__, constructor, and prototype during the import process. - Ensure that when modifying currentProviders, you access its .value property (e.g., currentProviders.value[id] = config). - Consider warning the user before overwriting existing configurations.

  function importProviderConfigurations(payload: Record<string, unknown>) {
    if (!isProviderConfigPayload(payload))
      throw new Error('Invalid provider configuration export format')

    const currentProviders = providersStore.providers
    for (const [id, config] of Object.entries(payload.providers)) {
      if (id === '__proto__' || id === 'constructor' || id === 'prototype')
        continue

      if (!providersStore.providerMetadata[id])
        continue

      if (config && typeof config === 'object') {
        currentProviders[id] = config
      }
    }

    if (payload.addedProviders) {
      for (const [id, added] of Object.entries(payload.addedProviders)) {
        if (id === '__proto__' || id === 'constructor' || id === 'prototype')
          continue

        if (!providersStore.providerMetadata[id])
          continue

        if (added) {
          providersStore.markProviderAdded(id)
        }
      }
    }
  }

Comment on lines +83 to +88
function isProviderConfigPayload(payload: unknown): payload is { format: string, providers: Record<string, Record<string, unknown>>, addedProviders: Record<string, boolean> } {
if (!payload || typeof payload !== 'object')
return false
return (payload as { format?: string }).format === 'provider-configurations:v1'
&& typeof (payload as any).providers === 'object'
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The type guard isProviderConfigPayload is incomplete. Its type predicate payload is { ..., addedProviders: Record<string, boolean> } indicates that addedProviders should be present, but the function body does not validate its existence or type. This discrepancy can lead to incorrect type narrowing. Please add a check for addedProviders to ensure the type guard is accurate and reliable.

Suggested change
function isProviderConfigPayload(payload: unknown): payload is { format: string, providers: Record<string, Record<string, unknown>>, addedProviders: Record<string, boolean> } {
if (!payload || typeof payload !== 'object')
return false
return (payload as { format?: string }).format === 'provider-configurations:v1'
&& typeof (payload as any).providers === 'object'
}
function isProviderConfigPayload(payload: unknown): payload is { format: string, providers: Record<string, Record<string, unknown>>, addedProviders: Record<string, boolean> } {
if (!payload || typeof payload !== 'object')
return false
return (payload as { format?: string }).format === 'provider-configurations:v1'
&& typeof (payload as any).providers === 'object'
&& typeof (payload as any).addedProviders === 'object'
}

Comment on lines +112 to +130
function handleProviderImport(event: Event) {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file)
return

file.text().then((raw) => {
const parsed = JSON.parse(raw) as Record<string, unknown>
importProviderConfigurations(parsed)
setStatus(t('settings.pages.data.status.providers_imported'))
importError.value = ''
}).catch((error) => {
console.error(error)
importError.value = t('settings.pages.data.status.providers_import_error')
setStatus(error instanceof Error ? error.message : String(error), 'error')
}).finally(() => {
target.value = ''
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency with the handleImport function in the same file, it's recommended to refactor handleProviderImport to use async/await instead of .then() chains. This will improve code readability and maintainability across the component.

async function handleProviderImport(event: Event) {
  const target = event.target as HTMLInputElement
  const file = target.files?.[0]
  if (!file)
    return

  try {
    const raw = await file.text()
    const parsed = JSON.parse(raw) as Record<string, unknown>
    importProviderConfigurations(parsed)
    setStatus(t('settings.pages.data.status.providers_imported'))
    importError.value = ''
  }
  catch (error) {
    console.error(error)
    importError.value = t('settings.pages.data.status.providers_import_error')
    setStatus(error instanceof Error ? error.message : String(error), 'error')
  }
  finally {
    target.value = ''
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature request: export and import for provider configurations

2 participants