Skip to content

fix(protocol): preserve provider import JSON values#17168

Merged
0xfullex merged 2 commits into
CherryHQ:mainfrom
VectorPeak:fix/provider-import-json-values
Jul 21, 2026
Merged

fix(protocol): preserve provider import JSON values#17168
0xfullex merged 2 commits into
CherryHQ:mainfrom
VectorPeak:fix/provider-import-json-values

Conversation

@VectorPeak

@VectorPeak VectorPeak commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Branch strategy - read before opening this PR

The v2 refactor has merged into main, so main is the default branch for active development (v1 and v2 code currently coexist there - expect large, breaking changes).

  • Active development (features, refactors, optimizations, fixes for the current codebase) -> target main (the default base).
  • v1 maintenance (hotfixes and subsequent v1 releases) -> branch from and target v1, not main.

A v1 fix does not auto-carry to main: if the same bug exists on main, open a separate forward-port PR targeting main. Before touching subsystems being replaced, read docs/references/data/ and watch for @deprecated markers - they flag code being deleted.

What this PR does

Before this PR:

Provider import deep links decoded the data query value and immediately ran the decoded payload through the legacy cleanup path before JSON.parse:

Buffer.from(data, 'base64')
  .toString('utf-8')
  .replaceAll("'", '"')
  .replaceAll('(', '')
  .replaceAll(')', '')

That kept legacy wrapped payloads like ({'id':'custom-openai'}) working, but the same cleanup also touched valid JSON string values. A standard JSON import payload containing a provider name such as Bob's OpenAI (EU) could be rewritten before parsing and fail instead of opening the provider settings flow.

After this PR:

Provider import decoding now treats standard JSON as the primary format. parseProvidersImportData first parses the decoded payload as-is, then falls back to the legacy cleanup path only if standard JSON parsing fails. This preserves apostrophes and parentheses inside valid JSON strings while keeping the existing wrapped/single-quote payload behavior covered by the test suite.

Why we need it and why it was done in this way

What Problem This Solves

parseProvidersImportData sits on the provider-import deep-link path:

cherrystudio://providers/api-keys?v=1&data=<base64 JSON>
  -> handleProvidersProtocolUrl
  -> parseProvidersImportData
  -> openSettingsInMainWindow('/settings/provider?addProviderData=...')

The bug was not in base64 decoding or settings navigation. It was the ordering inside parseProvidersImportData: a lossy legacy normalization step ran before the parser had a chance to accept already-valid JSON.

A valid JSON object can contain apostrophes and parentheses inside string values. For example, this provider name is valid JSON:

{ "id": "custom-openai", "name": "Bob's OpenAI (EU)" }

Before this PR, the decoded JSON text was rewritten into invalid JSON because the apostrophe became a double quote and the parentheses were removed:

{"id":"custom-openai","name":"Bob"s OpenAI EU"}

That means a normal provider import link could fail at parse time before the settings import screen receives addProviderData.

Changes

- const result = JSON.parse(
-   Buffer.from(data, 'base64').toString('utf-8').replaceAll("'", '"').replaceAll('(', '').replaceAll(')', '')
- )
+ const decoded = Buffer.from(data, 'base64').toString('utf-8')
+ let result: unknown
+
+ try {
+   result = JSON.parse(decoded)
+ } catch {
+   result = JSON.parse(decoded.replaceAll("'", '"').replaceAll('(', '').replaceAll(')', ''))
+ }

The fix keeps the change local to parseProvidersImportData, where the provider import payload is already decoded and normalized. It intentionally does not change provider deep-link query handling for + and /, settings navigation, or the legacy wrapped payload fallback.

Evidence

The new regression test covers the standard JSON payload that previously went through lossy cleanup:

const config = {
  id: 'custom-openai',
  name: "Bob's OpenAI (EU)"
}
const payload = Buffer.from(JSON.stringify(config), 'utf-8').toString('base64')

expect(parseProvidersImportData(payload)).toBe(JSON.stringify(config))

The existing compatibility test still covers the legacy wrapped payload:

const payload = Buffer.from("({'id':'custom-openai'})", 'utf-8').toString('base64')

expect(parseProvidersImportData(payload)).toBe(JSON.stringify({ id: 'custom-openai' }))

Validation performed:

node node_modules/vitest/vitest.mjs run --project main src/main/services/protocol/handlers/__tests__/providersImport.test.ts --reporter=verbose

Result: 5 passed.

Remote CI after the final push:

changes       pass
basic-checks  pass
general-test  pass

Possible call chain / impact

Provider import link
  -> ProtocolService routes hostname `providers`
  -> handleProvidersProtocolUrl('/api-keys')
  -> parseProvidersImportData(data)
  -> openSettingsInMainWindow('/settings/provider?addProviderData=...')

This PR only changes how provider import payload text is parsed after base64 decoding. It does not change MCP install links, navigate links, OAuth callbacks, renderer-side provider settings behavior, or the existing provider import handling for standard base64 + and / characters.

Breaking changes

NONE

Special notes for your reviewer

Validation performed:

  • git diff --check
  • node node_modules/vitest/vitest.mjs run --project main src/main/services/protocol/handlers/__tests__/providersImport.test.ts --reporter=verbose - 5 tests passed

Local note: the normal pnpm test:main src/main/services/protocol/handlers/__tests__/providersImport.test.ts path initially attempted to complete dependency installation and timed out fetching onnxruntime-node, so the focused Vitest run above was used after resolving the local test binary.

Checklist

This checklist is not enforcing, but it's a reminder of items that could be relevant to every PR.
Approvers are expected to review this list.

  • Branch: This PR targets the correct branch - main for active development, v1 for v1 maintenance fixes
  • PR: The PR description is expressive enough and will help future contributors
  • Code: Write code that humans can understand and Keep it simple
  • Refactor: You have left the code cleaner than you found it (Boy Scout Rule)
  • Upgrade: Impact of this change on upgrade flows was considered and addressed if required
  • Documentation: A user-guide update was considered and is present (link) or not required. Check this only when the PR introduces or changes a user-facing feature or behavior.
  • Self-review: I have reviewed my own code (e.g., via /gh-pr-review, gh pr diff, or GitHub UI) before requesting review from others

Release note

Fix provider import deep links so valid JSON values containing apostrophes or parentheses are preserved.

@VectorPeak
VectorPeak requested a review from 0xfullex as a code owner July 18, 2026 14:10
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
Signed-off-by: VectorPeak <[email protected]>
@VectorPeak
VectorPeak force-pushed the fix/provider-import-json-values branch from 61dc011 to ee33126 Compare July 18, 2026 14:32
@kangfenmao
kangfenmao requested a review from DeJeune July 19, 2026 05:48
@DeJeune DeJeune added the ready-to-merge This PR has sufficient reviewer approvals but is awaiting code owner approval. label Jul 21, 2026
@0xfullex
0xfullex merged commit fe479e4 into CherryHQ:main Jul 21, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge This PR has sufficient reviewer approvals but is awaiting code owner approval.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants