CLI: reuse OpenRouter credentials

This commit is contained in:
Matthew
2026-01-10 20:57:32 -05:00
committed by Peter Steinberger
parent b6982236a6
commit 7890bd7369
3 changed files with 176 additions and 7 deletions

View File

@@ -150,4 +150,75 @@ describe("applyAuthChoice", () => {
expect(result.config.models?.providers?.["opencode-zen"]).toBeUndefined();
expect(result.agentModelOverride).toBe("opencode/claude-opus-4-5");
});
it("uses existing OPENROUTER_API_KEY when selecting openrouter-api-key", async () => {
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-auth-"));
process.env.CLAWDBOT_STATE_DIR = tempStateDir;
process.env.CLAWDBOT_AGENT_DIR = path.join(tempStateDir, "agent");
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
process.env.OPENROUTER_API_KEY = "sk-openrouter-test";
const text = vi.fn();
const select: WizardPrompter["select"] = vi.fn(
async (params) => params.options[0]?.value as never,
);
const multiselect: WizardPrompter["multiselect"] = vi.fn(async () => []);
const confirm = vi.fn(async () => true);
const prompter: WizardPrompter = {
intro: vi.fn(noopAsync),
outro: vi.fn(noopAsync),
note: vi.fn(noopAsync),
select,
multiselect,
text,
confirm,
progress: vi.fn(() => ({ update: noop, stop: noop })),
};
const runtime: RuntimeEnv = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn((code: number) => {
throw new Error(`exit:${code}`);
}),
};
const result = await applyAuthChoice({
authChoice: "openrouter-api-key",
config: {},
prompter,
runtime,
setDefaultModel: true,
});
expect(confirm).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining("OPENROUTER_API_KEY"),
}),
);
expect(text).not.toHaveBeenCalled();
expect(result.config.auth?.profiles?.["openrouter:default"]).toMatchObject({
provider: "openrouter",
mode: "api_key",
});
expect(result.config.agents?.defaults?.model?.primary).toBe(
"openrouter/auto",
);
const authProfilePath = path.join(
tempStateDir,
"agents",
"main",
"agent",
"auth-profiles.json",
);
const raw = await fs.readFile(authProfilePath, "utf8");
const parsed = JSON.parse(raw) as {
profiles?: Record<string, { key?: string }>;
};
expect(parsed.profiles?.["openrouter:default"]?.key).toBe(
"sk-openrouter-test",
);
delete process.env.OPENROUTER_API_KEY;
});
});

View File

@@ -9,6 +9,7 @@ import {
CODEX_CLI_PROFILE_ID,
ensureAuthProfileStore,
listProfilesForProvider,
resolveAuthProfileOrder,
upsertAuthProfile,
} from "../agents/auth-profiles.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
@@ -371,16 +372,65 @@ export async function applyAuthChoice(params: {
"OpenAI API key",
);
} else if (params.authChoice === "openrouter-api-key") {
const key = await params.prompter.text({
message: "Enter OpenRouter API key",
validate: (value) => (value?.trim() ? undefined : "Required"),
const store = ensureAuthProfileStore(params.agentDir, {
allowKeychainPrompt: false,
});
await setOpenrouterApiKey(String(key).trim(), params.agentDir);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "openrouter:default",
const profileOrder = resolveAuthProfileOrder({
cfg: nextConfig,
store,
provider: "openrouter",
mode: "api_key",
});
const existingProfileId = profileOrder.find((profileId) =>
Boolean(store.profiles[profileId]),
);
const existingCred = existingProfileId
? store.profiles[existingProfileId]
: undefined;
let profileId = "openrouter:default";
let mode: "api_key" | "oauth" | "token" = "api_key";
let hasCredential = false;
if (existingProfileId && existingCred?.type) {
profileId = existingProfileId;
mode =
existingCred.type === "oauth"
? "oauth"
: existingCred.type === "token"
? "token"
: "api_key";
hasCredential = true;
}
if (!hasCredential) {
const envKey = resolveEnvApiKey("openrouter");
if (envKey) {
const useExisting = await params.prompter.confirm({
message: `Use existing OPENROUTER_API_KEY (${envKey.source})?`,
initialValue: true,
});
if (useExisting) {
await setOpenrouterApiKey(envKey.apiKey, params.agentDir);
hasCredential = true;
}
}
}
if (!hasCredential) {
const key = await params.prompter.text({
message: "Enter OpenRouter API key",
validate: (value) => (value?.trim() ? undefined : "Required"),
});
await setOpenrouterApiKey(String(key).trim(), params.agentDir);
hasCredential = true;
}
if (hasCredential) {
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId,
provider: "openrouter",
mode,
});
}
if (params.setDefaultModel) {
nextConfig = applyOpenrouterConfig(nextConfig);
await params.prompter.note(

View File

@@ -9,8 +9,11 @@ import {
applyAuthProfileConfig,
applyMinimaxApiConfig,
applyMinimaxApiProviderConfig,
applyOpenrouterConfig,
applyOpenrouterProviderConfig,
applyOpencodeZenConfig,
applyOpencodeZenProviderConfig,
OPENROUTER_DEFAULT_MODEL_REF,
writeOAuthCredentials,
} from "./onboard-auth.js";
@@ -301,3 +304,48 @@ describe("applyOpencodeZenConfig", () => {
]);
});
});
describe("applyOpenrouterProviderConfig", () => {
it("adds allowlist entry for the default model", () => {
const cfg = applyOpenrouterProviderConfig({});
const models = cfg.agents?.defaults?.models ?? {};
expect(Object.keys(models)).toContain(OPENROUTER_DEFAULT_MODEL_REF);
});
it("preserves existing alias for the default model", () => {
const cfg = applyOpenrouterProviderConfig({
agents: {
defaults: {
models: {
[OPENROUTER_DEFAULT_MODEL_REF]: { alias: "Router" },
},
},
},
});
expect(
cfg.agents?.defaults?.models?.[OPENROUTER_DEFAULT_MODEL_REF]?.alias,
).toBe("Router");
});
});
describe("applyOpenrouterConfig", () => {
it("sets correct primary model", () => {
const cfg = applyOpenrouterConfig({});
expect(cfg.agents?.defaults?.model?.primary).toBe(
OPENROUTER_DEFAULT_MODEL_REF,
);
});
it("preserves existing model fallbacks", () => {
const cfg = applyOpenrouterConfig({
agents: {
defaults: {
model: { fallbacks: ["anthropic/claude-opus-4-5"] },
},
},
});
expect(cfg.agents?.defaults?.model?.fallbacks).toEqual([
"anthropic/claude-opus-4-5",
]);
});
});