diff --git a/@omniroute/opencode-plugin/README.md b/@omniroute/opencode-plugin/README.md index 55aff38434..570ff4285a 100644 --- a/@omniroute/opencode-plugin/README.md +++ b/@omniroute/opencode-plugin/README.md @@ -30,7 +30,7 @@ omniroute setup opencode --auth # 3. Restart OpenCode — /models lists the full live catalog ``` -The `--auth` flag runs `opencode auth login --provider omniroute` automatically. +The `--auth` flag runs `opencode auth login --provider opencode-omniroute` automatically. Use `--base-url` to point at a non-default OmniRoute address: ```sh @@ -84,7 +84,7 @@ Peer dep: `@opencode-ai/plugin` (managed by your OpenCode install). ``` ```sh -opencode auth login --provider omniroute +opencode auth login --provider opencode-omniroute # prompts for the OmniRoute API key, writes to ~/.local/share/opencode/auth.json ``` @@ -164,8 +164,8 @@ Then in `~/.config/opencode/opencode.json` reference each directory by absolute Paths are relative to `~/.config/opencode/`. Each entry now resolves to a distinct module file, so OC loads them as two separate plugin instances. Authenticate each: ```sh -opencode auth login --provider omniroute -opencode auth login --provider omniroute-preprod +opencode auth login --provider opencode-omniroute +opencode auth login --provider opencode-omniroute-preprod ``` Each entry gets its own provider id, its own model picker entry, its own slot in `auth.json`, and its own TTL cache. Closures are isolated per plugin instance — no cross-talk. diff --git a/bin/cli/commands/setup-open-code.mjs b/bin/cli/commands/setup-open-code.mjs index dd20ba28a6..60f08158c2 100644 --- a/bin/cli/commands/setup-open-code.mjs +++ b/bin/cli/commands/setup-open-code.mjs @@ -218,6 +218,26 @@ function registerPluginInOpenCodeConfig({ * a clear "could not run opencode" message instead of a hard import * failure. */ +/** + * Resolve the provider id used for `opencode auth login --provider `. + * + * The bundled @omniroute/opencode-plugin registers its provider under + * `opencode-` (the `opencode-` prefix is required by OpenCode >=1.17.8's + * native-adapter gate). The auth login command must use the prefixed form + * because OpenCode resolves `--provider ` against the provider id the + * plugin actually registered. + * + * Idempotent: if the id already starts with `opencode-`, it passes through + * unchanged. This protects users who manually worked around the bug with + * `--provider opencode-omniroute`. + * + * @param {string} providerId + * @returns {string} + */ +export function resolveOpenCodeAuthProviderId(providerId) { + return providerId.startsWith("opencode-") ? providerId : `opencode-${providerId}`; +} + /** * Pure resolver for the `opencode auth login` spawn descriptor. Extracted so the * platform-branching logic is unit-testable without mocking child_process or @@ -231,21 +251,23 @@ function registerPluginInOpenCodeConfig({ */ export function resolveOpenCodeAuthSpawn(providerId, platform = process.platform) { const isWin = platform === "win32"; + const authProviderId = resolveOpenCodeAuthProviderId(providerId); return { command: isWin ? "opencode.cmd" : "opencode", - args: ["auth", "login", "--provider", providerId], + args: ["auth", "login", "--provider", authProviderId], options: { stdio: "inherit", shell: isWin }, }; } export function runOpenCodeAuth(providerId) { + const authProviderId = resolveOpenCodeAuthProviderId(providerId); const { command, args, options } = resolveOpenCodeAuthSpawn(providerId); const res = spawnSync(command, args, options); if (res.error) { // ENOENT = opencode is not on PATH if (res.error.code === "ENOENT") { printInfo( - `opencode CLI not found on PATH. Run \`opencode auth login --provider ${providerId}\` manually after installing OpenCode.` + `opencode CLI not found on PATH. Run \`opencode auth login --provider ${authProviderId}\` manually after installing OpenCode.` ); return 1; } @@ -343,7 +365,8 @@ export async function runSetupOpenCodeCommand(opts = {}) { if (wantsAuth) { if (nonInteractive) { printInfo(`Skipping \`opencode auth login\` (non-interactive mode).`); - printInfo(`Run manually: opencode auth login --provider ${providerId}`); + const authProviderId = resolveOpenCodeAuthProviderId(providerId); + printInfo(`Run manually: opencode auth login --provider ${authProviderId}`); } else { printHeading("Authenticating with OpenCode"); const authExit = runOpenCodeAuth(providerId); @@ -352,8 +375,9 @@ export async function runSetupOpenCodeCommand(opts = {}) { } } } else { + const authProviderId = resolveOpenCodeAuthProviderId(providerId); printInfo( - `Next step: opencode auth login --provider ${providerId} (pass --auth to do this automatically)` + `Next step: opencode auth login --provider ${authProviderId} (pass --auth to do this automatically)` ); } diff --git a/changelog.d/fixes/8830-fix.plan.md b/changelog.d/fixes/8830-fix.plan.md new file mode 100644 index 0000000000..5cb0cf3c28 --- /dev/null +++ b/changelog.d/fixes/8830-fix.plan.md @@ -0,0 +1 @@ +- fix(opencode): prefix provider id with "opencode-" for auth login command (#8830) \ No newline at end of file diff --git a/tests/unit/setup-open-code-win32-shell.test.mjs b/tests/unit/setup-open-code-win32-shell.test.mjs index 334fd2b14e..b6c93cf2a3 100644 --- a/tests/unit/setup-open-code-win32-shell.test.mjs +++ b/tests/unit/setup-open-code-win32-shell.test.mjs @@ -11,7 +11,10 @@ import { test } from "node:test"; import assert from "node:assert/strict"; -import { resolveOpenCodeAuthSpawn } from "../../bin/cli/commands/setup-open-code.mjs"; +import { + resolveOpenCodeAuthSpawn, + resolveOpenCodeAuthProviderId, +} from "../../bin/cli/commands/setup-open-code.mjs"; test("resolveOpenCodeAuthSpawn: win32 spawns opencode.cmd with shell:true (repro #7913)", () => { const spawn = resolveOpenCodeAuthSpawn("omniroute", "win32"); @@ -21,7 +24,7 @@ test("resolveOpenCodeAuthSpawn: win32 spawns opencode.cmd with shell:true (repro true, `expected shell:true on win32 (the EINVAL fix), got shell:${spawn.options.shell}` ); - assert.deepEqual(spawn.args, ["auth", "login", "--provider", "omniroute"]); + assert.deepEqual(spawn.args, ["auth", "login", "--provider", "opencode-omniroute"]); }); test("resolveOpenCodeAuthSpawn: linux/darwin spawn bare opencode with shell:false (no regression)", () => { @@ -36,7 +39,25 @@ test("resolveOpenCodeAuthSpawn: linux/darwin spawn bare opencode with shell:fals } }); -test("resolveOpenCodeAuthSpawn: forwards the provider id into the args", () => { +test("resolveOpenCodeAuthSpawn: prefixes provider id for auth login (#8830)", () => { const spawn = resolveOpenCodeAuthSpawn("anthropic", "linux"); - assert.deepEqual(spawn.args, ["auth", "login", "--provider", "anthropic"]); + assert.deepEqual(spawn.args, ["auth", "login", "--provider", "opencode-anthropic"]); +}); + +test("resolveOpenCodeAuthProviderId: adds opencode- prefix when absent (#8830)", () => { + assert.equal(resolveOpenCodeAuthProviderId("omniroute"), "opencode-omniroute"); + assert.equal(resolveOpenCodeAuthProviderId("omniroute-preprod"), "opencode-omniroute-preprod"); + assert.equal(resolveOpenCodeAuthProviderId("anthropic"), "opencode-anthropic"); +}); + +test("resolveOpenCodeAuthProviderId: idempotent — passes through already-prefixed ids (#8830)", () => { + assert.equal(resolveOpenCodeAuthProviderId("opencode-omniroute"), "opencode-omniroute"); + assert.equal( + resolveOpenCodeAuthProviderId("opencode-omniroute-preprod"), + "opencode-omniroute-preprod" + ); + assert.equal( + resolveOpenCodeAuthProviderId("opencode-anthropic"), + "opencode-anthropic" + ); });