Skip to content

fix(config): use package.json name for file:// plugin identity#16200

Open
coleleavitt wants to merge 4 commits intoanomalyco:devfrom
coleleavitt:coleleavitt/dev
Open

fix(config): use package.json name for file:// plugin identity#16200
coleleavitt wants to merge 4 commits intoanomalyco:devfrom
coleleavitt:coleleavitt/dev

Conversation

@coleleavitt
Copy link
Contributor

Issue for this PR

Closes #8759
Closes #10115
Closes #11159
Closes #12285
Closes #14304

Supersedes #8758 (my previous attempt, had conflicts)
Related to #15598 (similar fix by @cyberprophet)

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Problem: Multiple file:// plugins with the same entry filename (e.g., dist/index.js) were incorrectly deduplicated because getPluginName() only extracted the filename.

file:///path/to/plugin-a/dist/index.js -> "index"
file:///path/to/plugin-b/dist/index.js -> "index"  // COLLISION - silently dropped!

Solution: For file:// URLs, first look up the nearest package.json and use its name field. If no package.json exists, fall back to the full canonical URL (via realpathSync).

This approach:

  1. Properly deduplicates npm + file:// versions of the same plugin - both oh-my-opencode@2.4.3 and file:///path/to/oh-my-opencode/dist/index.js resolve to "oh-my-opencode" (from package.json)
  2. Prevents false collisions - multiple plugins with same filename but different paths get unique identities
  3. Matches Node.js ESM loader semantics - uses realpath for canonical paths
  4. Works cross-platform - handles Windows paths correctly

How did you verify your code works?

  1. bun test test/config/config.test.ts - all 65 tests pass
  2. Added new test cases:
    • file:// URL without package.json returns full canonical URL
    • file:// URL with package.json returns package name
    • file:// URL with scoped package.json returns scoped name
    • Multiple file:// plugins with same filename don't collide
    • file:// plugin with package.json dedupes with npm package of same name
  3. bun run typecheck passes for opencode package

Screenshots / recordings

N/A - non-UI change

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

When switching between models (e.g., Claude to ChatGPT), the SDK may be
created from @ai-sdk/openai-compatible which does not have a .responses()
method. This causes TypeError: sdk.responses is not a function.

Added fallback checks to openai, azure, and azure-cognitive-services
custom loaders to use sdk.languageModel() when sdk.responses is undefined.

Fixes model switching crashes.
The Codex plugin's custom fetch wrapper was mutating the global provider.options
object, which caused subsequent Anthropic model loads to fail with 'Bad Request'.

This happened because:
1. User switches to Codex (OpenAI) - custom fetch added to options
2. options.fetch mutation persists in provider.options
3. User switches to Anthropic - SDK cache key includes mutated options
4. Anthropic SDK loads with corrupted options, causing API errors

Fix: Create a local copy (sdkOptions) before adding the fetch wrapper,
preserving the original provider.options for other provider instances.

Fixes: Model switching from Codex to Anthropic causes API errors
Previously, file:// plugins were deduplicated by filename only, causing
collisions when multiple plugins used standard entry points like index.js.

Example:
  file:///path/to/plugin-a/dist/index.js -> 'index' (COLLISION)
  file:///path/to/plugin-b/dist/index.js -> 'index' (COLLISION)

This change:
- First checks for package.json 'name' field (up to 5 dirs up)
- Falls back to full canonical URL (via realpathSync) if no package.json
- Properly deduplicates file:// plugins with their npm counterparts

This matches Node.js ESM loader semantics and handles:
- Multiple plugins with same filename in different directories
- npm package + local file:// version of same plugin (deduplicates correctly)
- Symlinks resolved to canonical paths
- Cross-platform support (Windows + Unix)

Fixes anomalyco#8759, anomalyco#11159, anomalyco#14304
@cyberprophet
Copy link

Thanks for the mention — I previously tried to address the same collision in #15598.

The approach here (use nearest package.json name for file:// plugins, and fall back to canonicalized URL via realpath) looks like a solid way to avoid the "dist/index.js" filename collision while still deduping npm vs local dev installs.

One question/concern: this PR also changes packages/opencode/src/plugin/codex.ts and packages/opencode/src/provider/provider.ts. Those look orthogonal to getPluginName()/plugin deduplication — was that intentional? If not, I’d strongly recommend splitting them into a separate PR to keep review + risk surface tight.

Minor: findPackageJsonName() currently caps the upward search at 5 dirs; might be worth confirming that’s enough for typical layouts (dist/, build/, etc.).

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

The following comment was made by an LLM, it may be inaccurate:

Related PRs found:

  1. fix(config): avoid dedup collisions for file:// plugins in node_modules #15598 - "fix(config): avoid dedup collisions for file:// plugins in node_modules"

  2. fix(opencode): prevent plugin deduplication collision for index.js entry points #11161 - "fix(opencode): prevent plugin deduplication collision for index.js entry points"

These PRs are addressing the same underlying problem of plugin deduplication collisions. PR #16200 appears to be a more comprehensive fix that builds upon or supersedes these earlier attempts (the PR notes it "Supersedes #8758" and closes multiple related issues #8759, #10115, #11159, #12285, #14304).

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