[PR #4323] fix(provider): support local file paths for custom providers #10936

Closed
opened 2026-02-16 18:15:42 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/4323

State: closed
Merged: Yes


PR: Fix local file path support for custom providers

Summary

Fixes a bug where OpenCode cannot load custom providers from local file paths. Previously, all provider paths were treated as npm packages, causing BunInstallFailedError when trying to load local providers.

This implementation uses the same pattern as plugin loading for consistency.

Problem

When configuring a custom provider with a local path:

{
  "provider": {
    "my-provider": {
      "npm": "file://./my-provider"
    }
  }
}

OpenCode would attempt to install it via npm:

bun add --force --exact file://./my-provider@latest

This fails with:

BunInstallFailedError: package 'file://./my-provider' not found in registry

Root Cause

In packages/opencode/src/provider/provider.ts, the getSDK() function always called BunProc.install(pkg, "latest") without checking if pkg was a local path or npm package name.

// Before (broken):
const installedPath = await BunProc.install(pkg, "latest")  // ❌ Always tries npm install

Solution

Add detection for file:// prefix before attempting npm installation, matching the existing pattern in src/plugin/index.ts:

// After (fixed) - matches plugin loading pattern:
let installedPath: string
if (!pkg.startsWith("file://")) {
  installedPath = await BunProc.install(pkg, "latest")
} else {
  log.info("loading local provider", { pkg })
  installedPath = pkg
}

The fix:

  1. Checks for file:// prefix - same as plugin loading (consistent with existing codebase)
  2. If no prefix → npm install (existing behavior)
  3. If has prefix → use path directly for import

Testing

Before Fix (Bug Confirmed)

# With opencode v1.0.55 (before fix)
$ opencode run -m "my-provider/model" "test"

ProviderInitError: ProviderInitError
BunInstallFailedError: package 'file://./my-provider' not found in registry

After Fix (Working)

# With fix applied
$ opencode run -m "my-provider/model" "test"
✓ Works correctly - provider loaded from local path

Use Cases Enabled

  1. Development workflow: Test custom providers locally without publishing
  2. Private/internal providers: Use company-internal providers without npm registry
  3. Monorepo packages: Reference sibling packages in monorepo
  4. Custom integrations: Develop provider for proprietary LLM gateway

Configuration Example

{
  "provider": {
    "my-custom-provider": {
      "name": "My Custom Provider",
      "npm": "file://./providers/my-provider",
      "models": {
        "custom-model": { "name": "Custom Model" }
      }
    },
    "npm-package": {
      "npm": "@ai-sdk/anthropic"  // Still works as before
    }
  }
}

Note: Must use file:// prefix for local paths (matches plugin pattern)

Implementation Details

Matches Existing Plugin Pattern

This uses the exact same approach as plugin loading in src/plugin/index.ts:36:

// Plugin loading (existing):
if (!plugin.startsWith("file://")) {
  plugin = await BunProc.install(pkg, version)
}

// Provider loading (this PR):
if (!pkg.startsWith("file://")) {
  installedPath = await BunProc.install(pkg, "latest")
}

Benefits:

  • Consistent with existing codebase patterns
  • Simple and easy to understand
  • Minimal code changes (6 lines)

Backwards Compatibility

Fully backwards compatible

  • Existing npm package configurations work unchanged
  • No breaking changes to API or configuration format
  • Falls back to original behavior for non-file:// paths

Related

  • Addresses issue where users cannot develop custom providers locally
  • Uses same pattern as plugin loading for consistency
  • Requested by @rekram1-node to match existing codebase patterns

Files Changed

  • packages/opencode/src/provider/provider.ts - Added file:// prefix detection in getSDK() function

Commits

  • 3839d6a5 - Initial fix with multiple path formats
  • 2766b222 - Updated to match plugin loading pattern (current)

How to Test

  1. Create a simple test provider:
mkdir -p ./test-provider
cat > ./test-provider/index.js << 'EOF'
export function createTestProvider() {
  return {
    languageModel() {
      return {
        provider: 'test',
        modelId: 'test-model'
      }
    }
  }
}
EOF
  1. Configure OpenCode:
{
  "provider": {
    "test-local": {
      "name": "Test Local Provider",
      "npm": "file://./test-provider",
      "models": {
        "test-model": { "name": "Test Model" }
      }
    }
  }
}
  1. Test loading:
opencode models | grep test-local
# Should show: test-local/test-model

opencode run -m "test-local/test-model" "test"
# Should work without BunInstallFailedError

Checklist

  • Bug confirmed on latest dev branch
  • Fix implemented matching existing plugin pattern
  • No breaking changes
  • Backwards compatible with existing configs
  • Matches codebase style and patterns
  • Tested locally
  • Ready for review

Reviewer Notes:

  • Minimal change: only 6 lines (4 insertions, 1 deletion after refactor)
  • Uses exact same pattern as plugin loading for consistency
  • Simple logic: if not file://, npm install; otherwise use path
  • No changes to error handling, caching, or other provider loading logic
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/4323 **State:** closed **Merged:** Yes --- # PR: Fix local file path support for custom providers ## Summary Fixes a bug where OpenCode cannot load custom providers from local file paths. Previously, all provider paths were treated as npm packages, causing `BunInstallFailedError` when trying to load local providers. This implementation uses the same pattern as plugin loading for consistency. ## Problem When configuring a custom provider with a local path: ```json { "provider": { "my-provider": { "npm": "file://./my-provider" } } } ``` OpenCode would attempt to install it via npm: ```bash bun add --force --exact file://./my-provider@latest ``` This fails with: ``` BunInstallFailedError: package 'file://./my-provider' not found in registry ``` ## Root Cause In `packages/opencode/src/provider/provider.ts`, the `getSDK()` function always called `BunProc.install(pkg, "latest")` without checking if `pkg` was a local path or npm package name. ```typescript // Before (broken): const installedPath = await BunProc.install(pkg, "latest") // ❌ Always tries npm install ``` ## Solution Add detection for `file://` prefix before attempting npm installation, matching the existing pattern in `src/plugin/index.ts`: ```typescript // After (fixed) - matches plugin loading pattern: let installedPath: string if (!pkg.startsWith("file://")) { installedPath = await BunProc.install(pkg, "latest") } else { log.info("loading local provider", { pkg }) installedPath = pkg } ``` The fix: 1. **Checks for `file://` prefix** - same as plugin loading (consistent with existing codebase) 2. **If no prefix** → npm install (existing behavior) 3. **If has prefix** → use path directly for import ## Testing ### Before Fix (Bug Confirmed) ```bash # With opencode v1.0.55 (before fix) $ opencode run -m "my-provider/model" "test" ProviderInitError: ProviderInitError BunInstallFailedError: package 'file://./my-provider' not found in registry ``` ### After Fix (Working) ```bash # With fix applied $ opencode run -m "my-provider/model" "test" ✓ Works correctly - provider loaded from local path ``` ## Use Cases Enabled 1. **Development workflow**: Test custom providers locally without publishing 2. **Private/internal providers**: Use company-internal providers without npm registry 3. **Monorepo packages**: Reference sibling packages in monorepo 4. **Custom integrations**: Develop provider for proprietary LLM gateway ## Configuration Example ```json { "provider": { "my-custom-provider": { "name": "My Custom Provider", "npm": "file://./providers/my-provider", "models": { "custom-model": { "name": "Custom Model" } } }, "npm-package": { "npm": "@ai-sdk/anthropic" // Still works as before } } } ``` **Note**: Must use `file://` prefix for local paths (matches plugin pattern) ## Implementation Details ### Matches Existing Plugin Pattern This uses the **exact same approach** as plugin loading in `src/plugin/index.ts:36`: ```typescript // Plugin loading (existing): if (!plugin.startsWith("file://")) { plugin = await BunProc.install(pkg, version) } // Provider loading (this PR): if (!pkg.startsWith("file://")) { installedPath = await BunProc.install(pkg, "latest") } ``` **Benefits:** - ✅ Consistent with existing codebase patterns - ✅ Simple and easy to understand - ✅ Minimal code changes (6 lines) ## Backwards Compatibility ✅ **Fully backwards compatible** - Existing npm package configurations work unchanged - No breaking changes to API or configuration format - Falls back to original behavior for non-`file://` paths ## Related - Addresses issue where users cannot develop custom providers locally - Uses same pattern as plugin loading for consistency - Requested by @rekram1-node to match existing codebase patterns ## Files Changed - `packages/opencode/src/provider/provider.ts` - Added `file://` prefix detection in `getSDK()` function ## Commits - `3839d6a5` - Initial fix with multiple path formats - `2766b222` - Updated to match plugin loading pattern (current) ## How to Test 1. Create a simple test provider: ```bash mkdir -p ./test-provider cat > ./test-provider/index.js << 'EOF' export function createTestProvider() { return { languageModel() { return { provider: 'test', modelId: 'test-model' } } } } EOF ``` 2. Configure OpenCode: ```json { "provider": { "test-local": { "name": "Test Local Provider", "npm": "file://./test-provider", "models": { "test-model": { "name": "Test Model" } } } } } ``` 3. Test loading: ```bash opencode models | grep test-local # Should show: test-local/test-model opencode run -m "test-local/test-model" "test" # Should work without BunInstallFailedError ``` ## Checklist - [x] Bug confirmed on latest `dev` branch - [x] Fix implemented matching existing plugin pattern - [x] No breaking changes - [x] Backwards compatible with existing configs - [x] Matches codebase style and patterns - [x] Tested locally - [x] Ready for review --- **Reviewer Notes:** - Minimal change: only 6 lines (4 insertions, 1 deletion after refactor) - Uses exact same pattern as plugin loading for consistency - Simple logic: if not `file://`, npm install; otherwise use path - No changes to error handling, caching, or other provider loading logic
yindo added the pull-request label 2026-02-16 18:15:42 -05:00
yindo closed this issue 2026-02-16 18:15:42 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#10936