Merge pull request #703 from mteam88/openrouter-auth-config
Openrouter auth config (AI)
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
### New Features and Changes
|
### New Features and Changes
|
||||||
- CLI Backends: add Codex CLI fallback with resume support (text output) and JSONL parsing for new runs, plus a live CLI resume probe.
|
- CLI Backends: add Codex CLI fallback with resume support (text output) and JSONL parsing for new runs, plus a live CLI resume probe.
|
||||||
|
- CLI/Onboarding: add OpenRouter API key auth option in configure/onboard. (#703) — thanks @mteam88.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- CLI/Status: surface gateway provider runtime errors (Signal/iMessage/Slack) in the Providers table.
|
- CLI/Status: surface gateway provider runtime errors (Signal/iMessage/Slack) in the Providers table.
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ function installFailingFetchCapture() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("openai-responses reasoning replay", () => {
|
describe("openai-responses reasoning replay", () => {
|
||||||
it("replays reasoning for tool-call-only turns (required by OpenAI)", async () => {
|
it("skips reasoning for tool-call-only turns (OpenAI rejects standalone reasoning)", async () => {
|
||||||
const cap = installFailingFetchCapture();
|
const cap = installFailingFetchCapture();
|
||||||
try {
|
try {
|
||||||
const model = buildModel();
|
const model = buildModel();
|
||||||
@@ -141,11 +141,8 @@ describe("openai-responses reasoning replay", () => {
|
|||||||
)
|
)
|
||||||
.filter((t): t is string => typeof t === "string");
|
.filter((t): t is string => typeof t === "string");
|
||||||
|
|
||||||
expect(types).toContain("reasoning");
|
|
||||||
expect(types).toContain("function_call");
|
expect(types).toContain("function_call");
|
||||||
expect(types.indexOf("reasoning")).toBeLessThan(
|
expect(types).not.toContain("reasoning");
|
||||||
types.indexOf("function_call"),
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
cap.restore();
|
cap.restore();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,29 @@ describe("cli program", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("passes openrouter api key to onboard", async () => {
|
||||||
|
const program = buildProgram();
|
||||||
|
await program.parseAsync(
|
||||||
|
[
|
||||||
|
"onboard",
|
||||||
|
"--non-interactive",
|
||||||
|
"--auth-choice",
|
||||||
|
"openrouter-api-key",
|
||||||
|
"--openrouter-api-key",
|
||||||
|
"sk-openrouter-test",
|
||||||
|
],
|
||||||
|
{ from: "user" },
|
||||||
|
);
|
||||||
|
expect(onboardCommand).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
nonInteractive: true,
|
||||||
|
authChoice: "openrouter-api-key",
|
||||||
|
openrouterApiKey: "sk-openrouter-test",
|
||||||
|
}),
|
||||||
|
runtime,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("passes zai api key to onboard", async () => {
|
it("passes zai api key to onboard", async () => {
|
||||||
const program = buildProgram();
|
const program = buildProgram();
|
||||||
await program.parseAsync(
|
await program.parseAsync(
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ export function buildProgram() {
|
|||||||
.option("--mode <mode>", "Wizard mode: local|remote")
|
.option("--mode <mode>", "Wizard mode: local|remote")
|
||||||
.option(
|
.option(
|
||||||
"--auth-choice <choice>",
|
"--auth-choice <choice>",
|
||||||
"Auth: setup-token|claude-cli|token|openai-codex|openai-api-key|codex-cli|antigravity|gemini-api-key|zai-api-key|apiKey|minimax-cloud|minimax-api|minimax|opencode-zen|skip",
|
"Auth: setup-token|claude-cli|token|openai-codex|openai-api-key|openrouter-api-key|codex-cli|antigravity|gemini-api-key|zai-api-key|apiKey|minimax-cloud|minimax-api|minimax|opencode-zen|skip",
|
||||||
)
|
)
|
||||||
.option(
|
.option(
|
||||||
"--token-provider <id>",
|
"--token-provider <id>",
|
||||||
@@ -269,6 +269,7 @@ export function buildProgram() {
|
|||||||
)
|
)
|
||||||
.option("--anthropic-api-key <key>", "Anthropic API key")
|
.option("--anthropic-api-key <key>", "Anthropic API key")
|
||||||
.option("--openai-api-key <key>", "OpenAI API key")
|
.option("--openai-api-key <key>", "OpenAI API key")
|
||||||
|
.option("--openrouter-api-key <key>", "OpenRouter API key")
|
||||||
.option("--gemini-api-key <key>", "Gemini API key")
|
.option("--gemini-api-key <key>", "Gemini API key")
|
||||||
.option("--zai-api-key <key>", "Z.AI API key")
|
.option("--zai-api-key <key>", "Z.AI API key")
|
||||||
.option("--minimax-api-key <key>", "MiniMax API key")
|
.option("--minimax-api-key <key>", "MiniMax API key")
|
||||||
@@ -315,6 +316,7 @@ export function buildProgram() {
|
|||||||
| "token"
|
| "token"
|
||||||
| "openai-codex"
|
| "openai-codex"
|
||||||
| "openai-api-key"
|
| "openai-api-key"
|
||||||
|
| "openrouter-api-key"
|
||||||
| "codex-cli"
|
| "codex-cli"
|
||||||
| "antigravity"
|
| "antigravity"
|
||||||
| "gemini-api-key"
|
| "gemini-api-key"
|
||||||
@@ -332,6 +334,7 @@ export function buildProgram() {
|
|||||||
tokenExpiresIn: opts.tokenExpiresIn as string | undefined,
|
tokenExpiresIn: opts.tokenExpiresIn as string | undefined,
|
||||||
anthropicApiKey: opts.anthropicApiKey as string | undefined,
|
anthropicApiKey: opts.anthropicApiKey as string | undefined,
|
||||||
openaiApiKey: opts.openaiApiKey as string | undefined,
|
openaiApiKey: opts.openaiApiKey as string | undefined,
|
||||||
|
openrouterApiKey: opts.openrouterApiKey as string | undefined,
|
||||||
geminiApiKey: opts.geminiApiKey as string | undefined,
|
geminiApiKey: opts.geminiApiKey as string | undefined,
|
||||||
zaiApiKey: opts.zaiApiKey as string | undefined,
|
zaiApiKey: opts.zaiApiKey as string | undefined,
|
||||||
minimaxApiKey: opts.minimaxApiKey as string | undefined,
|
minimaxApiKey: opts.minimaxApiKey as string | undefined,
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export function buildAuthChoiceOptions(params: {
|
|||||||
label: "OpenAI Codex (ChatGPT OAuth)",
|
label: "OpenAI Codex (ChatGPT OAuth)",
|
||||||
});
|
});
|
||||||
options.push({ value: "openai-api-key", label: "OpenAI API key" });
|
options.push({ value: "openai-api-key", label: "OpenAI API key" });
|
||||||
|
options.push({ value: "openrouter-api-key", label: "OpenRouter API key" });
|
||||||
options.push({
|
options.push({
|
||||||
value: "antigravity",
|
value: "antigravity",
|
||||||
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",
|
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ describe("applyAuthChoice", () => {
|
|||||||
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||||
|
const previousOpenrouterKey = process.env.OPENROUTER_API_KEY;
|
||||||
let tempStateDir: string | null = null;
|
let tempStateDir: string | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -37,6 +38,11 @@ describe("applyAuthChoice", () => {
|
|||||||
} else {
|
} else {
|
||||||
process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
|
process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
|
||||||
}
|
}
|
||||||
|
if (previousOpenrouterKey === undefined) {
|
||||||
|
delete process.env.OPENROUTER_API_KEY;
|
||||||
|
} else {
|
||||||
|
process.env.OPENROUTER_API_KEY = previousOpenrouterKey;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prompts and writes MiniMax API key when selecting minimax-api", async () => {
|
it("prompts and writes MiniMax API key when selecting minimax-api", async () => {
|
||||||
@@ -150,4 +156,75 @@ describe("applyAuthChoice", () => {
|
|||||||
expect(result.config.models?.providers?.["opencode-zen"]).toBeUndefined();
|
expect(result.config.models?.providers?.["opencode-zen"]).toBeUndefined();
|
||||||
expect(result.agentModelOverride).toBe("opencode/claude-opus-4-5");
|
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;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
CODEX_CLI_PROFILE_ID,
|
CODEX_CLI_PROFILE_ID,
|
||||||
ensureAuthProfileStore,
|
ensureAuthProfileStore,
|
||||||
listProfilesForProvider,
|
listProfilesForProvider,
|
||||||
|
resolveAuthProfileOrder,
|
||||||
upsertAuthProfile,
|
upsertAuthProfile,
|
||||||
} from "../agents/auth-profiles.js";
|
} from "../agents/auth-profiles.js";
|
||||||
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
|
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
|
||||||
@@ -44,12 +45,16 @@ import {
|
|||||||
applyMinimaxProviderConfig,
|
applyMinimaxProviderConfig,
|
||||||
applyOpencodeZenConfig,
|
applyOpencodeZenConfig,
|
||||||
applyOpencodeZenProviderConfig,
|
applyOpencodeZenProviderConfig,
|
||||||
|
applyOpenrouterConfig,
|
||||||
|
applyOpenrouterProviderConfig,
|
||||||
applyZaiConfig,
|
applyZaiConfig,
|
||||||
MINIMAX_HOSTED_MODEL_REF,
|
MINIMAX_HOSTED_MODEL_REF,
|
||||||
|
OPENROUTER_DEFAULT_MODEL_REF,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setGeminiApiKey,
|
setGeminiApiKey,
|
||||||
setMinimaxApiKey,
|
setMinimaxApiKey,
|
||||||
setOpencodeZenApiKey,
|
setOpencodeZenApiKey,
|
||||||
|
setOpenrouterApiKey,
|
||||||
setZaiApiKey,
|
setZaiApiKey,
|
||||||
writeOAuthCredentials,
|
writeOAuthCredentials,
|
||||||
ZAI_DEFAULT_MODEL_REF,
|
ZAI_DEFAULT_MODEL_REF,
|
||||||
@@ -366,6 +371,77 @@ export async function applyAuthChoice(params: {
|
|||||||
`Saved OPENAI_API_KEY to ${result.path} for launchd compatibility.`,
|
`Saved OPENAI_API_KEY to ${result.path} for launchd compatibility.`,
|
||||||
"OpenAI API key",
|
"OpenAI API key",
|
||||||
);
|
);
|
||||||
|
} else if (params.authChoice === "openrouter-api-key") {
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const profileOrder = resolveAuthProfileOrder({
|
||||||
|
cfg: nextConfig,
|
||||||
|
store,
|
||||||
|
provider: "openrouter",
|
||||||
|
});
|
||||||
|
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(
|
||||||
|
`Default model set to ${OPENROUTER_DEFAULT_MODEL_REF}`,
|
||||||
|
"Model configured",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
nextConfig = applyOpenrouterProviderConfig(nextConfig);
|
||||||
|
agentModelOverride = OPENROUTER_DEFAULT_MODEL_REF;
|
||||||
|
await noteAgentModel(OPENROUTER_DEFAULT_MODEL_REF);
|
||||||
|
}
|
||||||
} else if (params.authChoice === "openai-codex") {
|
} else if (params.authChoice === "openai-codex") {
|
||||||
const isRemote = isRemoteEnvironment();
|
const isRemote = isRemoteEnvironment();
|
||||||
await params.prompter.note(
|
await params.prompter.note(
|
||||||
@@ -745,6 +821,8 @@ export function resolvePreferredProviderForAuthChoice(
|
|||||||
return "openai-codex";
|
return "openai-codex";
|
||||||
case "openai-api-key":
|
case "openai-api-key":
|
||||||
return "openai";
|
return "openai";
|
||||||
|
case "openrouter-api-key":
|
||||||
|
return "openrouter";
|
||||||
case "gemini-api-key":
|
case "gemini-api-key":
|
||||||
return "google";
|
return "google";
|
||||||
case "antigravity":
|
case "antigravity":
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import {
|
|||||||
applyMinimaxApiProviderConfig,
|
applyMinimaxApiProviderConfig,
|
||||||
applyOpencodeZenConfig,
|
applyOpencodeZenConfig,
|
||||||
applyOpencodeZenProviderConfig,
|
applyOpencodeZenProviderConfig,
|
||||||
|
applyOpenrouterConfig,
|
||||||
|
applyOpenrouterProviderConfig,
|
||||||
|
OPENROUTER_DEFAULT_MODEL_REF,
|
||||||
writeOAuthCredentials,
|
writeOAuthCredentials,
|
||||||
} from "./onboard-auth.js";
|
} 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",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export async function setMinimaxApiKey(key: string, agentDir?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
|
export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
|
||||||
|
export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";
|
||||||
|
|
||||||
export async function setZaiApiKey(key: string, agentDir?: string) {
|
export async function setZaiApiKey(key: string, agentDir?: string) {
|
||||||
// Write to the multi-agent path so gateway finds credentials on startup
|
// Write to the multi-agent path so gateway finds credentials on startup
|
||||||
@@ -145,6 +146,18 @@ export async function setZaiApiKey(key: string, agentDir?: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setOpenrouterApiKey(key: string, agentDir?: string) {
|
||||||
|
upsertAuthProfile({
|
||||||
|
profileId: "openrouter:default",
|
||||||
|
credential: {
|
||||||
|
type: "api_key",
|
||||||
|
provider: "openrouter",
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function applyZaiConfig(cfg: ClawdbotConfig): ClawdbotConfig {
|
export function applyZaiConfig(cfg: ClawdbotConfig): ClawdbotConfig {
|
||||||
const models = { ...cfg.agents?.defaults?.models };
|
const models = { ...cfg.agents?.defaults?.models };
|
||||||
models[ZAI_DEFAULT_MODEL_REF] = {
|
models[ZAI_DEFAULT_MODEL_REF] = {
|
||||||
@@ -175,6 +188,51 @@ export function applyZaiConfig(cfg: ClawdbotConfig): ClawdbotConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyOpenrouterProviderConfig(
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
): ClawdbotConfig {
|
||||||
|
const models = { ...cfg.agents?.defaults?.models };
|
||||||
|
models[OPENROUTER_DEFAULT_MODEL_REF] = {
|
||||||
|
...models[OPENROUTER_DEFAULT_MODEL_REF],
|
||||||
|
alias: models[OPENROUTER_DEFAULT_MODEL_REF]?.alias ?? "OpenRouter",
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
agents: {
|
||||||
|
...cfg.agents,
|
||||||
|
defaults: {
|
||||||
|
...cfg.agents?.defaults,
|
||||||
|
models,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyOpenrouterConfig(cfg: ClawdbotConfig): ClawdbotConfig {
|
||||||
|
const next = applyOpenrouterProviderConfig(cfg);
|
||||||
|
const existingModel = next.agents?.defaults?.model;
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
agents: {
|
||||||
|
...next.agents,
|
||||||
|
defaults: {
|
||||||
|
...next.agents?.defaults,
|
||||||
|
model: {
|
||||||
|
...(existingModel &&
|
||||||
|
"fallbacks" in (existingModel as Record<string, unknown>)
|
||||||
|
? {
|
||||||
|
fallbacks: (existingModel as { fallbacks?: string[] })
|
||||||
|
.fallbacks,
|
||||||
|
}
|
||||||
|
: undefined),
|
||||||
|
primary: OPENROUTER_DEFAULT_MODEL_REF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function applyAuthProfileConfig(
|
export function applyAuthProfileConfig(
|
||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -36,11 +36,13 @@ import {
|
|||||||
applyMinimaxConfig,
|
applyMinimaxConfig,
|
||||||
applyMinimaxHostedConfig,
|
applyMinimaxHostedConfig,
|
||||||
applyOpencodeZenConfig,
|
applyOpencodeZenConfig,
|
||||||
|
applyOpenrouterConfig,
|
||||||
applyZaiConfig,
|
applyZaiConfig,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setGeminiApiKey,
|
setGeminiApiKey,
|
||||||
setMinimaxApiKey,
|
setMinimaxApiKey,
|
||||||
setOpencodeZenApiKey,
|
setOpencodeZenApiKey,
|
||||||
|
setOpenrouterApiKey,
|
||||||
setZaiApiKey,
|
setZaiApiKey,
|
||||||
} from "./onboard-auth.js";
|
} from "./onboard-auth.js";
|
||||||
import {
|
import {
|
||||||
@@ -264,6 +266,25 @@ export async function runNonInteractiveOnboarding(
|
|||||||
});
|
});
|
||||||
process.env.OPENAI_API_KEY = key;
|
process.env.OPENAI_API_KEY = key;
|
||||||
runtime.log(`Saved OPENAI_API_KEY to ${result.path}`);
|
runtime.log(`Saved OPENAI_API_KEY to ${result.path}`);
|
||||||
|
} else if (authChoice === "openrouter-api-key") {
|
||||||
|
const resolved = await resolveNonInteractiveApiKey({
|
||||||
|
provider: "openrouter",
|
||||||
|
cfg: baseConfig,
|
||||||
|
flagValue: opts.openrouterApiKey,
|
||||||
|
flagName: "--openrouter-api-key",
|
||||||
|
envVar: "OPENROUTER_API_KEY",
|
||||||
|
runtime,
|
||||||
|
});
|
||||||
|
if (!resolved) return;
|
||||||
|
if (resolved.source !== "profile") {
|
||||||
|
await setOpenrouterApiKey(resolved.key);
|
||||||
|
}
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "openrouter:default",
|
||||||
|
provider: "openrouter",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
nextConfig = applyOpenrouterConfig(nextConfig);
|
||||||
} else if (authChoice === "minimax-cloud") {
|
} else if (authChoice === "minimax-cloud") {
|
||||||
const resolved = await resolveNonInteractiveApiKey({
|
const resolved = await resolveNonInteractiveApiKey({
|
||||||
provider: "minimax",
|
provider: "minimax",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type AuthChoice =
|
|||||||
| "token"
|
| "token"
|
||||||
| "openai-codex"
|
| "openai-codex"
|
||||||
| "openai-api-key"
|
| "openai-api-key"
|
||||||
|
| "openrouter-api-key"
|
||||||
| "codex-cli"
|
| "codex-cli"
|
||||||
| "antigravity"
|
| "antigravity"
|
||||||
| "apiKey"
|
| "apiKey"
|
||||||
@@ -43,6 +44,7 @@ export type OnboardOptions = {
|
|||||||
tokenExpiresIn?: string;
|
tokenExpiresIn?: string;
|
||||||
anthropicApiKey?: string;
|
anthropicApiKey?: string;
|
||||||
openaiApiKey?: string;
|
openaiApiKey?: string;
|
||||||
|
openrouterApiKey?: string;
|
||||||
geminiApiKey?: string;
|
geminiApiKey?: string;
|
||||||
zaiApiKey?: string;
|
zaiApiKey?: string;
|
||||||
minimaxApiKey?: string;
|
minimaxApiKey?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user