Files
magpie/internal/plugin/testdata/fake/index.js
T
yetone a31f97b30d plugin: OpenCode's provider plugins sign in to subscriptions in magpie — an npm package OpenCode users install for a plan (its auth hook: loader, methods, oauth authorize/callback, api keys, chat.headers) runs in a Bun host magpie downloads the first time a plugin needs it, speaking JSON-RPC over stdio; its sign-ins are kept in plugin-auth.json (0600). A provider a plugin signs in to is a provider like the others: its models the plugin lists, each spoken to on the API its AI SDK package speaks (anthropic → Messages, openai/azure → Responses, google → Gemini, Copilot's GPT-5+ → Responses, else chat completions), its requests carried by the plugin's fetch to the base URL its loader gives; an id magpie already has is <id>-plugin. CLI: magpie plugin [list|add|rm|update|on|off|login|logout]. App: Settings → Plugins adds, updates, switches off and removes them and says why one didn't load; Add provider → From plugins signs in with the plugin's methods, its questions (picks and checked text), the browser and a pasted code or a key, followed as the built-in sign-ins are; removing the account signs out. The built-in subscriptions stay. TestFakePlugin, TestPluginProvider (chat, Anthropic and Gemini models through the gateway) and TestPluginSignIn (a wrong code fails, the right one signs in, removing signs out, a key) run a fake plugin on Bun; plugin-signin.test.cjs (Chromium, WebKit, en/zh) covers the add sheet and Settings. signin-callback, add-sheet, zed-sub, account-proxy, own-signin-remove, routing-signin, click-scroll GUI tests pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, TestClaudeIdentityServedWhileAsked, known flakes under load, pass alone). Not yet tried with a real plugin and account.
2026-09-30 16:44:32 +08:00

59 lines
2.4 KiB
JavaScript

// A plugin as OpenCode's are written, signing in to a made-up provider
// whose requests go to $FAKE_BASE.
export const FakePlugin = async ({ client }) => ({
config: async (cfg) => {
cfg.provider = cfg.provider ?? {}
cfg.provider.fakeco = {
name: "FakeCo",
npm: "@ai-sdk/openai-compatible",
api: "https://fake.invalid/v1",
models: {
"fake-1": { name: "Fake One", limit: { context: 1000, output: 100 } },
"fake-claude": { name: "Fake Claude", provider: { npm: "@ai-sdk/anthropic" }, limit: { context: 2000, output: 200 } },
"fake-gemini": { name: "Fake Gemini", provider: { npm: "@ai-sdk/google" }, reasoning: true, limit: { context: 3000, output: 300 } },
},
}
},
auth: {
provider: "fakeco",
methods: [
{ type: "api", label: "API key" },
{
type: "oauth",
label: "Browser",
prompts: [
{ type: "select", key: "where", message: "Where?", options: [{ label: "Home", value: "home" }, { label: "Work", value: "work" }] },
{ type: "text", key: "team", message: "Team?", when: { key: "where", op: "eq", value: "work" }, validate: (v) => (v ? undefined : "Required") },
],
authorize: async (inputs) => ({
url: "https://fake.invalid/auth?where=" + inputs.where,
instructions: "Paste the code",
method: "code",
callback: async (code) =>
code === "good"
? { type: "success", refresh: "r-" + (inputs.team ?? "none"), access: "stale", expires: 0, accountId: "me@fake" }
: { type: "failed" },
}),
},
],
loader: async (getAuth, provider) => ({
apiKey: "dummy",
baseURL: process.env.FAKE_BASE,
async fetch(url, init) {
let a = await getAuth()
if (a.type === "oauth" && a.expires < Date.now()) {
a = { ...a, access: "fresh", expires: Date.now() + 3600e3 }
await client.auth.set({ path: { id: "fakeco" }, body: a })
}
const h = new Headers(init.headers)
h.set("authorization", "Bearer " + (a.type === "oauth" ? a.access : a.key))
h.set("x-models", Object.keys(provider.models).sort().join(","))
return fetch(url, { ...init, headers: h })
},
}),
},
"chat.headers": async (input, output) => {
if (input.model.providerID === "fakeco") output.headers["x-plugin-model"] = input.model.id
},
})