Files
magpie/docs/subsystems/provider-plugins.md
T
yetone bc00ee1df2 plugin, provider: a program a plugin starts has magpie's proxy, and a CLI login with no link says why (𝕏 on Discord)
The Grok plugin's sign-in said only "grok login gave no link to open".
The plugin host has magpie's proxy only as MAGPIE_*_PROXY, so the
`grok login` the plugin runs went out with none, where the built-in's had
it (netproxy.Env); where x.ai is reached only through a proxy, it failed.
host.js's spawn wrapper now gives a started program HTTPS_PROXY and
HTTP_PROXY (the account's own proxy, none for direct, else magpie's) when
the plugin set none.

grok 1.0.46, unable to reach x.ai, prints "Error: error sending request
for url (https://auth.x.ai/oauth2/device/code): …", whose URL the
built-in's runCLISignIn took for the page to open; a line saying error
gives no link, and a login with none ends with the CLI's last line. The
plugin does the same from 0.1.8, which the grok mover now asks for.

plugin_cliproxy_test.go and TestCLISignInSaysWhyNoLink fail before this.
Checked with grok 1.0.46 itself through the plugin host and the published
plugin's code, given a proxy that refuses: the CLI goes through it and the
error names what it said.
2026-10-05 22:25:33 +08:00

8.4 KiB

Provider and plugin ownership

Some built-in subscriptions are deprecated and have community plugins that provide the same service. A subscription can still use its built-in implementation or move to its plugin. That ownership determines where sign-in, refresh, model discovery, requests, usage, and errors are handled.

Responsibilities and sources of truth

Part Responsibility Source
Provider migration Match subscriptions to plugins, record ownership, transfer accounts, and maintain required plugin versions migrate.go and migrate_*.go in the same directory
Provider integration Expose plugin-backed provider cards, usage, and sign-in plugins.go, plugin_usage.go, pluginsignin.go
Plugin host Load plugins and execute their code internal/plugin, host.js
Gateway Dispatch requests through the implementation that owns the subscription internal/gateway
GUI Present available subscriptions, migration state, and plugin sign-in app.js
Community plugin Implement the moved subscription's upstream behavior magpie-community/plugins, packages/<name>

The movers map assembled in internal/provider/migrate*.go is the authoritative subscription-to-plugin mapping. Each mover's min is the minimum plugin version required by magpie. The list of moved subscriptions in AGENTS.md is a contributor lookup; confirm the mapping in source when changing it.

Ownership and runtime paths

provider.Moved(id) identifies a subscription whose migration state in migrations.json is plugin. In /api/providers, its row exposes move.state == "plugin"; the GUI also receives its id in onPlugins.

Operation Built-in owns the subscription Plugin owns the subscription
Sign-in, refresh, and models Built-in provider implementation Community plugin through magpie's plugin integration
Upstream requests and translation Built-in gateway adapter Plugin fetch through the plugin host
Usage and upstream errors Built-in implementation Plugin, exposed through magpie's provider integration
Migration state and required version Magpie Magpie

For a moved subscription, its built-in upstream implementation does not run. Plugin accounts use Account.Agent == "plugin"; requests reach plugin.Fetch through Provider.Do and bypass the built-in subscription adapter. Magpie still handles routing, protocol conversion, quota handling, and compatibility adjustments, as well as the plugin host, provider integration, migration code, and GUI plugin paths.

How ownership changes

Migration records use four states:

State Meaning
moving Migration is in progress or was interrupted. An explicit retry first rolls back the interrupted move.
plugin The plugin owns the built-in provider id. This is the only state recognized by Moved.
back Ownership returned to the built-in. Automatic handover leaves it there.
failed Migration failed and the built-in continues serving. Automatic retry waits six hours.

The editor's "Move to plugin" action calls provider.Adopt. With accounts, adoption delegates to Move, which imports and validates accounts and model coverage before committing plugin ownership. With no accounts, adoption verifies the package and the provider it serves, then records plugin ownership without account checks.

Installing the plugin independently can also hand over ownership through provider.HandOver. The package must be enabled, meet the mover's minimum version, and serve the expected provider. After add, update, or upgrade, the GUI calls HandOver(ctx, false), which can adopt a built-in with no accounts. Existing built-in accounts wait for HandOver(ctx, true) in gateway maintenance.

KeepRetiringMoved first runs after 20 seconds, then hourly. Each cycle maintains moved plugin versions, calls MoveRetiring, then performs automatic handover. MoveRetiring moves subscriptions listed in provider.Retiring that have built-in accounts, preserving move-back choices and waiting six hours after a failed attempt. That list is currently empty. Handover preserves a plugin already signed in under its separate -plugin id and skips existing migration records except a failed record old enough to retry.

MoveBack restores built-in ownership and records back. Compatible accounts added through the plugin can return too; incompatible accounts can remain with the plugin under its separate id. Removing or disabling a plugin first moves affected built-ins back and stops if that fails. A package installed by the user remains after moving back; a migration-installed package is removed only when no remaining signed-in or moved provider needs it.

New users do not see an unused deprecated built-in in the Add sheet. unusedSub leaves it out when it has no account. Until an installed replacement takes ownership, replacedSub makes the Add sheet show the plugin tile. The Plugins page is the installation entry point.

keepMovedCurrent maintains the required plugin version for moved subscriptions. Raising the owning mover's min after publishing a plugin fix triggers this update path. Update failures are logged; disabled packages and local-folder installations are not automatically replaced.

Constraints for fixes

Determine ownership before choosing the fix location. A bug affecting a moved subscription usually needs a change in the community plugin. A built-in-only fix does not reach moved users.

For a plugin fix, publish the corrected community package and raise the owning mover's min to that version. Change the built-in too when the fix must reach users who have not moved.

Keep the migration, host, and upstream behavior separate when investigating failures. A plugin bug can require a community change; a host, provider-card, sign-in orchestration, or migration bug can require a magpie change even for moved users.

The host gets magpie's proxy only as MAGPIE_*_PROXY and applies it to each fetch itself. A program a plugin starts, such as the Grok plugin's grok login, gets it back as HTTPS_PROXY/HTTP_PROXY from the spawn wrapper in host.js, unless the plugin set one. A sign-in that works in the built-in and fails through the plugin may be a host difference like this one, not a plugin bug.

The GUI's plugin integration includes subOf, pluginSubs, and startPluginSignIn. A change to sign-in or provider presentation must follow these paths as well as the built-in paths it affects.

Verification

migrate_notice_test.go includes TestMovedBuiltinsSayTheirPlugin, which checks that moved built-ins tell contributors which plugin serves them. Migration tests live beside migrate.go.

Gateway parity tests in plugin_parity_test.go and other plugin_*_test.go files compare or exercise plugin paths. A built-in test alone does not establish moved-user behavior. Inspect each test's fixture to confirm that it covers the provider and operation being changed.

Run the relevant package tests with an isolated home directory. Resolve GOPATH and Go's caches before changing HOME, so repeated runs reuse dependencies and downloaded toolchains. The subshell makes temporary files writable and cleans up its home on exit, including when tests fail, and returns the test command's exit status.

(
	verification_home=$(mktemp -d) || exit
	trap 'chmod -R u+w "$verification_home"; rm -rf "$verification_home"' EXIT
	verification_gopath=$(go env GOPATH) || exit
	verification_modcache=$(go env GOMODCACHE) || exit
	verification_buildcache=$(go env GOCACHE) || exit
	HOME="$verification_home" GOPATH="$verification_gopath" GOMODCACHE="$verification_modcache" GOCACHE="$verification_buildcache" \
		go test -tags nogui ./internal/provider ./internal/gateway ./internal/plugin
)

Some migration and gateway plugin tests require Bun on PATH and use local plugin fixtures; they skip when Bun is unavailable. Report any skipped tests and verify the community package separately when its implementation changes.