Skip to content

refactor: simplify provider detection and resolution - #21

Merged
emilsvennesson merged 3 commits into
mainfrom
refactor/simplify-provider-detection-and-resolution
Apr 29, 2026
Merged

emilsvennesson merged 3 commits into
mainfrom
refactor/simplify-provider-detection-and-resolution

Conversation

@emilsvennesson

Copy link
Copy Markdown
Owner

Summary

Replaces the multi-strategy provider detection logic and the deeply-layered
resolution helpers with a single canonical-ID lookup and two small functions.
Net −549 lines; bundle size drops from 37.18 KB → 31.91 KB. No behavior
change for any supported provider.

Why

Provider detection layered three independent strategies (npm-package matching,
ID-substring hints, per-model uniformity heuristics) plus five fallback
helpers. OpenCode already exposes canonical, well-known provider IDs
(anthropic, openai, github-copilot, moonshotai, moonshotai-cn) via
provider.id and the chat.message hook's model.providerID. A flat ID map
covers every supported case and makes the rest of the pipeline obvious.

The resolution side had its own bloat: a 6-helper chain for what is a 3-step
priority lookup, a ProviderState wrapper around a single field, a
hasAnyProvider guard that listed every provider type by hand, and three
full provider scans per cold path.

What changed

Detection

  • src/providers/registry.ts: replaced detectProviderTypeFromNpm,
    detectProviderTypeFromProviderID, detectProviderTypeFromModel, hint
    arrays, and helpers with a flat PROVIDER_TYPES_BY_ID map and a single
    detectProviderType lookup. Added moonshotai-cn.
  • model.api.npm is no longer consulted anywhere.
  • Custom-renamed providers (e.g. my-anthropic-proxy) are intentionally not
    auto-detected; users must use the canonical provider ID. Custom baseURL
    on canonical providers continues to work.

Resolution (src/index.ts)

  • Replaced 6 helpers (resolveSearchProviderresolveLockedModel/
    resolveActiveModel/resolveFallbackModelresolveModelByPriority +
    buildSearchConfig) with findModelByKey + pickModel.
  • Moved the openai → chatgpt redirect into resolveActiveType, so
    resolution no longer needs a special branch.
  • Replaced hasAnyProvider (23 lines) with
    Object.keys(resolutions).length === NO_PROVIDERS.
  • Inlined hasConfiguredOpenAIBaseURL + shouldAttachChatGPTResolution
    (17 lines) as resolutions.openai?.credentials.baseURL?.trim().
  • Dropped ProviderState wrapper and ResolvedProvider interface.
  • Replaced inline structural typing of client with PluginInput["client"].

Scanning (src/config.ts)

  • scanProviders now runs once per cold path; loadResolutions threads
    the scan state through chatgpt/copilot attachment instead of calling
    resolveModelOverrides (deleted) twice more.
  • 4-way repetition (createInitialScanState, buildResolutionMap,
    ScanState) collapsed via a SCANNABLE_TYPES array.
  • Dropped isScannableProviderType guard (detectProviderType returns
    ScannableProviderType | null directly).
  • resolveGeneralModelHint/resolveCopilotModelHint → constants.

Auth (src/providers/{chatgpt,copilot}/auth.ts)

  • Extracted shared readAuthEntry<Entry>(client, directory, key),
    PathClient, and path helpers into src/providers/shared/auth.ts.
  • Each adapter's auth.ts is now ~50–75 lines focused on the entry
    shape and buildCredentials. Removes ~170 lines of duplication.

Types (src/types.ts)

  • Dropped providerType field from ProviderResolution (redundant with
    the map key).
  • Dropped SearchArgs interface; adapters now take query: string
    directly. Updated all 5 adapter signatures and dispatchSearch.
  • Moved ScannableProviderType from config.ts to types.ts.
  • ProviderResolutionMap is now Partial<Record<ProviderType, ...>>.

Lint

  • Enabled ternaries (no-ternary: off); updated AGENTS.md accordingly.

File-by-file impact

File Before After Δ
src/index.ts 416 214 −202
src/config.ts 363 264 −99
src/providers/registry.ts 112 45 −67
src/providers/chatgpt/auth.ts 134 53 −81
src/providers/copilot/auth.ts 157 76 −81
src/types.ts 104 94 −10
src/providers/shared/auth.ts (new) 82 +82
Bundle (dist/index.js) 37.18 KB 31.91 KB −14%

Behavior preserved

  • All five supported provider types: anthropic, openai, github-copilot,
    moonshotai (now also moonshotai-cn).
  • Custom baseURL on canonical provider IDs (provider-level options and
    per-model api.url), including Anthropic /v1$ stripping.
  • ChatGPT OAuth detection (only when openai has no configured baseURL).
  • Copilot enterprise URL via OAuth enterpriseUrl.
  • websearch: "always" / "auto" flags.
  • Locked → active → fallback resolution priority.

Behavior intentionally removed

  • Auto-detection of custom-renamed provider IDs (e.g.
    my-anthropic-proxy configured with npm: "@ai-sdk/anthropic"). Users
    must use the canonical OpenCode provider ID.

Verification

bun run check  # format:check + lint + typecheck → all pass
bun run build  # ESM bundle 31.91 KB + .d.ts → success

No tests exist yet.

Detect providers by canonical OpenCode ID instead of layering npm-package
matching, ID-substring hints, and per-model uniformity heuristics. The
provider type now comes from a single flat ID-to-type lookup, with
moonshotai-cn added alongside moonshotai.

Collapses the 6-helper resolution chain to two functions, moves the
ChatGPT-vs-OpenAI routing into detection, drops the redundant
ProviderResolution.providerType field and ProviderState wrapper, scans
providers once per cold path instead of three times, and extracts the
duplicated chatgpt/copilot auth.json reading into a shared helper.

Net -549 lines, no behavior change for any supported provider.
Copilot AI review requested due to automatic review settings April 29, 2026 19:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the web-search plugin’s provider detection and model resolution pipeline to rely on canonical OpenCode provider IDs and a simplified scan/resolve flow, while also de-duplicating shared auth.json reading logic.

Changes:

  • Replaced multi-strategy provider detection with a canonical provider-id → adapter-type map (detectProviderType) and consolidated scan state (scanProviders).
  • Simplified model selection/resolution logic to a small set of functions (resolveActiveType, findModelByKey, pickModel) and reduced repeated provider scanning.
  • Extracted shared OpenCode auth.json reading into src/providers/shared/auth.ts and updated provider adapters to accept query: string directly.

Reviewed changes

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

Show a summary per file
File Description
src/index.ts Simplifies resolution loading, active-provider routing (incl. OpenAI→ChatGPT), and model selection.
src/config.ts Collapses provider scanning and resolution map building; removes npm-based detection inputs.
src/providers/registry.ts Introduces canonical provider-id lookup map and exports scan/type priority constants.
src/types.ts Refactors shared types (adds ScannableProviderType, removes SearchArgs, simplifies resolution map typing).
src/providers/shared/auth.ts New shared helper to locate and read OpenCode auth.json entries.
src/providers/chatgpt/auth.ts Uses shared auth reader; trims and simplifies OAuth credential extraction.
src/providers/copilot/auth.ts Uses shared auth reader; refactors enterprise URL normalization and credential extraction.
src/providers/index.ts Updates adapter interface to accept query: string and streamlines dispatch functions.
src/providers/anthropic/index.ts Updates adapter signature to executeSearch(config, query).
src/providers/openai/index.ts Updates adapter signature to executeSearch(config, query).
src/providers/moonshot/index.ts Updates adapter signature to executeSearch(config, query).
src/providers/chatgpt/index.ts Updates adapter signature to executeSearch(config, query).
src/providers/copilot/index.ts Updates adapter signature to executeSearch(config, query).
AGENTS.md Updates style guidance to allow ternaries in simple cases.
.oxlintrc.json Disables no-ternary to match updated style guidance.

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

Comment thread src/index.ts Outdated
Comment on lines 131 to 137
const copilot = await resolveCopilotCredentials(client, directory);
if (copilot) {
resolutions.copilot = {
credentials: copilotCredentials,
fallbackModel: modelOverrides.fallbackModel,
lockedModel: modelOverrides.lockedModel,
providerType: "copilot",
credentials: copilot,
fallbackModel: scan.copilot.fallbackModel,
lockedModel: scan.copilot.lockedModel,
};
Comment thread src/providers/registry.ts Outdated
Comment on lines +22 to +24
* Provider types that are scannable from OpenCode provider config,
* in the order they should be preferred when multiple providers offer
* a `lockedModel` or `fallbackModel`.
Comment thread src/providers/registry.ts
Comment on lines +13 to +19
const PROVIDER_TYPES_BY_ID: Record<string, ScannableProviderType> = {
anthropic: "anthropic",
"github-copilot": "copilot",
moonshotai: "moonshot",
"moonshotai-cn": "moonshot",
openai: "openai",
};
Switch from a type-keyed resolution map to a list of resolutions keyed by
OpenCode provider ID. This lets users have multiple providers of the same
type (e.g. `openai-prod` and `openai-staging`) with their own credentials
and baseURLs, all auto-detected via npm-package matching on
`@ai-sdk/openai`, `@ai-sdk/anthropic`, or `@ai-sdk/github-copilot`.

Detection now falls back to npm matching when the provider ID is not
canonical. Active model routing matches by provider ID, so each renamed
provider serves traffic on its own credentials. ChatGPT OAuth shadowing
is scoped to the canonical `openai` provider with no baseURL; renamed
openai-typed providers keep their explicit credentials.
…feedback

Allow scanProvider to emit credential-less scan results when a provider
exposes websearch flags but no apiKey, so an OAuth-only github-copilot
config that pins a model via `"websearch": "always"` is no longer
silently dropped. The OAuth attachment phase fills in credentials with
a unified baseURL guard that also preserves any explicit Copilot proxy
configuration. Use the canonical `moonshotai` provider ID in the
formatNoProviderError example block.
@emilsvennesson
emilsvennesson merged commit fb85188 into main Apr 29, 2026
1 check passed
@emilsvennesson
emilsvennesson deleted the refactor/simplify-provider-detection-and-resolution branch April 29, 2026 20:29
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.

2 participants