Skip to content

fix: OpenCode config path should prefer opencode.jsonc over opencode.json - #83

Open
yan-6 wants to merge 5 commits into
freestylefly:mainfrom
yan-6:fix/issue-60
Open

yan-6 wants to merge 5 commits into
freestylefly:mainfrom
yan-6:fix/issue-60

Conversation

@yan-6

@yan-6 yan-6 commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Problem

WeSight hardcoded the OpenCode config file path to ~/.config/opencode/opencode.json, but OpenCode v1.17.x creates and uses ~/.config/opencode/opencode.jsonc. WeSight therefore failed to read a valid config and always prompted that no model is configured.

readJsonObject() also used bare JSON.parse(), which cannot handle JSONC (comments, trailing commas), so fixing only the path would still fail to parse.

Root Cause

  • getOpenCodeConfigPath() in externalAgentProviderStore.ts always returned opencode.json
  • Inline path resolution in externalAgentEnvironment.ts and externalAgentConfigSync.ts also hardcoded opencode.json
  • Each of those three modules has its own private readJsonObject, all using bare JSON.parse

Fix

  1. Config path resolution — prefer opencode.jsonc when present, falling back to opencode.json for older versions, matching OpenCode's own loading order. Applied in all three modules.

  2. Shared JSONC parsing — new src/main/libs/jsoncUtil.ts exposing stripJsonComments, parseJsonObjectText, and readJsonOrJsoncObject. All three readJsonObject helpers route through it, so every module that can now resolve to a .jsonc file can also parse one.

    stripJsonComments is a single-pass, string-aware scanner rather than a regex, so comment-like sequences inside string values are preserved. A naive // regex corrupts "https://opencode.ai/config.json" into "https: and makes JSON.parse throw — which would silently defeat the whole fix, since nearly every real config contains a URL. Trailing commas are also removed.

Files Changed

  • src/main/libs/jsoncUtil.ts (new) — shared JSONC scanner and readers
  • src/main/libs/jsoncUtil.test.ts (new) — 9 tests
  • src/main/libs/externalAgentProviderStore.ts.jsonc preference; delegates to shared util; re-exports stripJsonComments
  • src/main/libs/externalAgentEnvironment.ts.jsonc preference + JSONC-capable read
  • src/main/libs/externalAgentConfigSync.ts.jsonc preference + JSONC-capable read
  • src/main/libs/externalAgentProviderStoreJsonc.test.ts — 8 tests

Self-review

  • All three hardcoded opencode.json locations from the issue are fixed, and all three can now parse what they resolve to. Pointing a module at .jsonc without giving it a JSONC parser would have degraded readOpenCodeConfigSummary (empty provider/model detection) and readOpenCodeLocalConfig (spurious "缺少可导入的 API Key" errors, plus a sync path writing from an empty base).
  • Falls back to opencode.json when .jsonc is absent, so older installs are unaffected.
  • Comment stripping is gated on the .jsonc extension; plain .json parsing is byte-for-byte unchanged.
  • Failures still degrade safely to null through the existing try/catch.
  • No new dependencies.

Verification (local, macOS)

  • 17 JSONC tests total — URL-in-string preservation, line/block comments, string-internal comment markers, trailing commas, escaped quotes, nested baseURL, escaped Windows paths, plain-JSON passthrough, missing file, malformed content, array root, extension gating
  • npx tsc --noEmit → exit 0
  • npx eslint on all changed files → clean
  • npm test77 files / 568 tests passed (was 76 / 559)

Closes #60

@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

@yan-6 is attempting to deploy a commit to the canghe's projects Team on Vercel.

A member of the Team first needs to authorize it.

OpenCode v1.17.x creates opencode.jsonc as the primary config file
but WeSight hardcoded the path to opencode.json. This caused
getOpenCodeConfigPath() to return a path that does not exist, so
readJsonObject() returned null and no providers were detected.

Changes:
- getOpenCodeConfigPath() now checks for opencode.jsonc first and
  falls back to opencode.json, matching OpenCode's own load order.
- readJsonObject() strips JSONC-style comments before JSON.parse
  so opencode.jsonc files with // or /* */ comments parse correctly.

Fixes freestylefly#60
freestylefly and others added 3 commits September 6, 2026 09:08
…figSync

Extends the opencode.jsonc preference fix to the two remaining files
that hardcoded opencode.json in their getCliConfigPaths() helpers:
- externalAgentEnvironment.ts (line ~1025)
- externalAgentConfigSync.ts (line ~647)

Both now prefer opencode.jsonc when it exists, matching the same
pattern already applied in externalAgentProviderStore.ts.
The previous regex-based stripper removed everything after any '//'
sequence, including inside string values. That corrupted common
opencode.jsonc content such as "$schema": "https://opencode.ai/..."
and "baseURL": "https://api.deepseek.com/v1", so JSON.parse threw and
readJsonObject() returned null - the exact failure mode issue freestylefly#60 reports.

Replace it with a single-pass scanner that tracks string and escape state,
so comments are only removed outside string literals. Also drop trailing
commas, which JSONC permits but JSON.parse rejects.

Adds 8 regression tests covering URLs in values, block comments,
comment markers inside strings, trailing commas, escaped quotes,
nested baseURL values, and escaped Windows paths.
@yan-6

yan-6 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up self-review (2026-09-10): fixed a latent bug in the JSONC stripper

A re-review of this branch found that the previous comment-stripping implementation was itself broken for the exact input this PR aims to support.

The bug

The stripper used:

raw.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '')

The second regex is not string-aware, so it deletes everything after any // — including inside string values. A typical opencode.jsonc contains:

{
  "$schema": "https://opencode.ai/config.json",
  // preferred model
  "model": "deepseek/deepseek-chat",
  "provider": {
    "deepseek": { "options": { "baseURL": "https://api.deepseek.com/v1" } }
  }
}

After stripping, "https://opencode.ai/config.json" became "https: — an unterminated string. JSON.parse threw, readJsonObject() returned null, and WeSight still showed "needs model configuration". In other words, the fix would not have resolved #60 for any config containing a URL, which in practice means almost all of them.

Verified against 8 representative inputs: the old regex failed 5 of 8.

The fix

Replaced the regex pair with a single-pass scanner that tracks string and escape state, so // and /* */ are only treated as comments outside string literals. Also strips trailing commas, which JSONC permits but JSON.parse rejects.

stripJsonComments is now exported so it can be tested directly.

Verification

  • New test file externalAgentProviderStoreJsonc.test.ts — 8 regression tests: URLs in values, block comments, comment markers inside strings, trailing commas, escaped quotes before a comment, nested baseURL, escaped Windows paths, plain JSON passthrough. All pass.
  • npx tsc --noEmit — exit 0
  • npx eslint on both changed files — exit 0
  • npm test — 76 files / 559 tests passed (was 75 / 551; +1 file, +8 tests)

Self-review conclusion

The path-resolution part of this PR (prefer opencode.jsonc, fall back to opencode.json, applied consistently across externalAgentProviderStore.ts, externalAgentEnvironment.ts, externalAgentConfigSync.ts) was already correct and is unchanged. The parsing half is now actually functional rather than nominally present. Backward compatibility with plain .json is preserved — the scanner only runs for .jsonc paths. Failure behaviour is unchanged: parse errors still fall through the existing try/catch to null.

…e.jsonc

PR freestylefly#83 taught three modules to prefer opencode.jsonc, but only
externalAgentProviderStore could parse JSONC. externalAgentEnvironment and
externalAgentConfigSync still used bare JSON.parse, so once a user had a
commented opencode.jsonc their auth status and config import silently
fell back to empty.

Extract stripJsonComments into a shared jsoncUtil module and route all three
readJsonObject helpers through readJsonOrJsoncObject.
@yan-6

yan-6 commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up: the .jsonc preference was only half-wired

Self-review found a second gap in this PR, distinct from the earlier string-aware comment-stripping fix.

This PR taught three modules to prefer opencode.jsonc:

  • externalAgentProviderStore.ts
  • externalAgentEnvironment.ts
  • externalAgentConfigSync.ts

But only the first one gained JSONC parsing. Each module has its own private readJsonObject, and the other two still called bare JSON.parse:

// externalAgentEnvironment.ts and externalAgentConfigSync.ts (before this commit)
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));

So for a user whose opencode.jsonc contains any comment, the path change actively made two code paths worse — they now resolve to a file they cannot parse:

  • buildCliConfigSnapshotreadOpenCodeConfigSummary returns empty, so provider/model detection and auth status silently degrade
  • readOpenCodeLocalConfig / syncOpenCodeGlobalConfigFromWesightModel see {} and throw "本机 OpenCode 配置缺少可导入的 API Key", and the sync path would overwrite the user's config from an empty base

Fix in 5bd3f93

Extracted the scanner into src/main/libs/jsoncUtil.ts (stripJsonComments, parseJsonObjectText, readJsonOrJsoncObject) and routed all three readJsonObject helpers through it. stripJsonComments stays exported from externalAgentProviderStore so the existing test import keeps working.

Verification (local, macOS)

  • New jsoncUtil.test.ts: 9 tests — URL-in-string preservation, line/block comments, trailing commas, missing file, malformed content, array root, and .json vs .jsonc extension gating
  • npx tsc --noEmit → exit 0
  • npx eslint on all changed files → clean
  • npm test77 files / 568 tests passed (was 76 / 559)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] OpenCode 本地配置无法被识别:WeSight 读取的是 opencode.json,但 OpenCode 实际使用 opencode.jsonc

1 participant