fix: honor apiKey auth-choice tokens (#1485) (thanks @iHildy)
This commit is contained in:
parent
15c865f340
commit
7c9a839ff6
@ -32,6 +32,7 @@ Docs: https://docs.clawd.bot
|
||||
- Control UI: resolve local avatar URLs with basePath across injection + identity RPC. (#1457) Thanks @dlauer.
|
||||
- Agents: sanitize assistant history text to strip tool-call markers. (#1456) Thanks @zerone0x.
|
||||
- Discord: clarify Message Content Intent onboarding hint. (#1487) Thanks @kyleok.
|
||||
- Onboarding: honor `--auth-choice apiKey` token provider + token flags to skip prompts. (#1485) Thanks @iHildy.
|
||||
- Agents: surface concrete API error details instead of generic AI service errors.
|
||||
- Exec: fall back to non-PTY when PTY spawn fails (EBADF). (#1484)
|
||||
- Exec approvals: allow per-segment allowlists for chained shell commands on gateway + node hosts. (#1458) Thanks @czekaj.
|
||||
|
||||
@ -295,8 +295,8 @@ Options:
|
||||
- `--mode <local|remote>`
|
||||
- `--flow <quickstart|advanced|manual>` (manual is an alias for advanced)
|
||||
- `--auth-choice <setup-token|claude-cli|token|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|codex-cli|gemini-api-key|zai-api-key|apiKey|minimax-api|opencode-zen|skip>`
|
||||
- `--token-provider <id>` (non-interactive; used with `--auth-choice token`)
|
||||
- `--token <token>` (non-interactive; used with `--auth-choice token`)
|
||||
- `--token-provider <id>` (non-interactive; used with `--auth-choice token` or `apiKey`)
|
||||
- `--token <token>` (non-interactive; used with `--auth-choice token` or `apiKey`)
|
||||
- `--token-profile-id <id>` (non-interactive; default: `<provider>:manual`)
|
||||
- `--token-expires-in <duration>` (non-interactive; e.g. `365d`, `12h`)
|
||||
- `--anthropic-api-key <key>`
|
||||
|
||||
@ -56,9 +56,12 @@ export function registerOnboardCommand(program: Command) {
|
||||
)
|
||||
.option(
|
||||
"--token-provider <id>",
|
||||
"Token provider id (non-interactive; used with --auth-choice token)",
|
||||
"Token provider id (non-interactive; used with --auth-choice token or apiKey)",
|
||||
)
|
||||
.option(
|
||||
"--token <token>",
|
||||
"Token value (non-interactive; used with --auth-choice token or apiKey)",
|
||||
)
|
||||
.option("--token <token>", "Token value (non-interactive; used with --auth-choice token)")
|
||||
.option(
|
||||
"--token-profile-id <id>",
|
||||
"Auth profile id (non-interactive; default: <provider>:manual)",
|
||||
|
||||
183
src/commands/auth-choice.api-key-flags.test.ts
Normal file
183
src/commands/auth-choice.api-key-flags.test.ts
Normal file
@ -0,0 +1,183 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import { AUTH_STORE_VERSION } from "../agents/auth-profiles/constants.js";
|
||||
import { applyAuthChoice } from "./auth-choice.js";
|
||||
|
||||
const noopAsync = async () => {};
|
||||
const noop = () => {};
|
||||
const authProfilePathFor = (agentDir: string) => path.join(agentDir, "auth-profiles.json");
|
||||
|
||||
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
||||
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||
const previousAnthropicKey = process.env.ANTHROPIC_API_KEY;
|
||||
const previousOpenaiKey = process.env.OPENAI_API_KEY;
|
||||
let tempStateDir: string | null = null;
|
||||
|
||||
async function setupTempState() {
|
||||
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;
|
||||
await fs.mkdir(process.env.CLAWDBOT_AGENT_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
function buildPrompter() {
|
||||
const text = vi.fn(async () => "");
|
||||
const confirm = vi.fn(async () => false);
|
||||
const prompter: WizardPrompter = {
|
||||
intro: vi.fn(noopAsync),
|
||||
outro: vi.fn(noopAsync),
|
||||
note: vi.fn(noopAsync),
|
||||
select: vi.fn(async () => "" as never),
|
||||
multiselect: vi.fn(async () => []),
|
||||
text,
|
||||
confirm,
|
||||
progress: vi.fn(() => ({ update: noop, stop: noop })),
|
||||
};
|
||||
return { prompter, text, confirm };
|
||||
}
|
||||
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn((code: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
}),
|
||||
};
|
||||
|
||||
afterEach(async () => {
|
||||
if (tempStateDir) {
|
||||
await fs.rm(tempStateDir, { recursive: true, force: true });
|
||||
tempStateDir = null;
|
||||
}
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.CLAWDBOT_STATE_DIR;
|
||||
} else {
|
||||
process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
||||
}
|
||||
if (previousAgentDir === undefined) {
|
||||
delete process.env.CLAWDBOT_AGENT_DIR;
|
||||
} else {
|
||||
process.env.CLAWDBOT_AGENT_DIR = previousAgentDir;
|
||||
}
|
||||
if (previousPiAgentDir === undefined) {
|
||||
delete process.env.PI_CODING_AGENT_DIR;
|
||||
} else {
|
||||
process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
|
||||
}
|
||||
if (previousAnthropicKey === undefined) {
|
||||
delete process.env.ANTHROPIC_API_KEY;
|
||||
} else {
|
||||
process.env.ANTHROPIC_API_KEY = previousAnthropicKey;
|
||||
}
|
||||
if (previousOpenaiKey === undefined) {
|
||||
delete process.env.OPENAI_API_KEY;
|
||||
} else {
|
||||
process.env.OPENAI_API_KEY = previousOpenaiKey;
|
||||
}
|
||||
});
|
||||
|
||||
describe("applyAuthChoice with apiKey flags", () => {
|
||||
it("uses provided openrouter token when authChoice=apiKey", async () => {
|
||||
await setupTempState();
|
||||
const agentDir = process.env.CLAWDBOT_AGENT_DIR ?? "";
|
||||
const authProfilePath = authProfilePathFor(agentDir);
|
||||
await fs.writeFile(
|
||||
authProfilePath,
|
||||
JSON.stringify({
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openrouter:legacy": {
|
||||
type: "oauth",
|
||||
provider: "openrouter",
|
||||
access: "access",
|
||||
refresh: "refresh",
|
||||
expires: Date.now() + 60_000,
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const { prompter, text, confirm } = buildPrompter();
|
||||
const result = await applyAuthChoice({
|
||||
authChoice: "apiKey",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider: "openrouter",
|
||||
token: "sk-openrouter-flag",
|
||||
},
|
||||
});
|
||||
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
expect(result.config.auth?.profiles?.["openrouter:default"]).toMatchObject({
|
||||
provider: "openrouter",
|
||||
mode: "api_key",
|
||||
});
|
||||
|
||||
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-flag");
|
||||
});
|
||||
|
||||
it("uses provided openai token when authChoice=apiKey", async () => {
|
||||
await setupTempState();
|
||||
const { prompter, text, confirm } = buildPrompter();
|
||||
|
||||
await applyAuthChoice({
|
||||
authChoice: "apiKey",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider: "openai",
|
||||
token: "sk-openai-flag",
|
||||
},
|
||||
});
|
||||
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
expect(process.env.OPENAI_API_KEY).toBe("sk-openai-flag");
|
||||
const envPath = path.join(process.env.CLAWDBOT_STATE_DIR ?? "", ".env");
|
||||
const envContents = await fs.readFile(envPath, "utf8");
|
||||
expect(envContents).toContain("OPENAI_API_KEY=sk-openai-flag");
|
||||
});
|
||||
|
||||
it("uses provided anthropic token when authChoice=apiKey", async () => {
|
||||
await setupTempState();
|
||||
process.env.ANTHROPIC_API_KEY = "sk-env-test";
|
||||
const { prompter, text, confirm } = buildPrompter();
|
||||
|
||||
await applyAuthChoice({
|
||||
authChoice: "apiKey",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider: "anthropic",
|
||||
token: "sk-anthropic-flag",
|
||||
},
|
||||
});
|
||||
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
|
||||
const authProfilePath = authProfilePathFor(process.env.CLAWDBOT_AGENT_DIR ?? "");
|
||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as { profiles?: Record<string, { key?: string }> };
|
||||
expect(parsed.profiles?.["anthropic:default"]?.key).toBe("sk-anthropic-flag");
|
||||
});
|
||||
});
|
||||
@ -8,6 +8,7 @@ import {
|
||||
normalizeApiKeyInput,
|
||||
validateApiKeyInput,
|
||||
} from "./auth-choice.api-key.js";
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||
import { buildTokenProfileId, validateAnthropicSetupToken } from "./auth-token.js";
|
||||
import { applyAuthProfileConfig, setAnthropicApiKey } from "./onboard-auth.js";
|
||||
@ -198,19 +199,22 @@ export async function applyAuthChoiceAnthropic(
|
||||
}
|
||||
|
||||
if (params.authChoice === "apiKey") {
|
||||
if (params.opts?.tokenProvider && params.opts.tokenProvider !== "anthropic") {
|
||||
const tokenProvider = params.opts?.tokenProvider
|
||||
? normalizeProviderId(params.opts.tokenProvider)
|
||||
: undefined;
|
||||
const explicitToken =
|
||||
tokenProvider === "anthropic" ? normalizeApiKeyInput(params.opts?.token ?? "") : "";
|
||||
if (tokenProvider && tokenProvider !== "anthropic") {
|
||||
return null;
|
||||
}
|
||||
|
||||
let nextConfig = params.config;
|
||||
let hasCredential = false;
|
||||
const envKey = process.env.ANTHROPIC_API_KEY?.trim();
|
||||
|
||||
if (params.opts?.token) {
|
||||
await setAnthropicApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
if (explicitToken) {
|
||||
await setAnthropicApiKey(explicitToken, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential && envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing ANTHROPIC_API_KEY (env, ${formatApiKeyPreview(envKey)})?`,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ensureAuthProfileStore, resolveAuthProfileOrder } from "../agents/auth-profiles.js";
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import {
|
||||
formatApiKeyPreview,
|
||||
@ -6,6 +7,7 @@ import {
|
||||
validateApiKeyInput,
|
||||
} from "./auth-choice.api-key.js";
|
||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||
import type { AuthChoice } from "./onboard-types.js";
|
||||
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
||||
import {
|
||||
applyGoogleGeminiModelDefault,
|
||||
@ -56,84 +58,86 @@ export async function applyAuthChoiceApiProviders(
|
||||
);
|
||||
};
|
||||
|
||||
const tokenProvider = params.opts?.tokenProvider
|
||||
? normalizeProviderId(params.opts.tokenProvider)
|
||||
: undefined;
|
||||
const tokenValue = params.opts?.token;
|
||||
let authChoice = params.authChoice;
|
||||
if (
|
||||
authChoice === "apiKey" &&
|
||||
params.opts?.tokenProvider &&
|
||||
params.opts.tokenProvider !== "anthropic" &&
|
||||
params.opts.tokenProvider !== "openai"
|
||||
tokenProvider &&
|
||||
tokenProvider !== "anthropic" &&
|
||||
tokenProvider !== "openai"
|
||||
) {
|
||||
if (params.opts.tokenProvider === "openrouter") {
|
||||
authChoice = "openrouter-api-key";
|
||||
} else if (params.opts.tokenProvider === "vercel-ai-gateway") {
|
||||
authChoice = "ai-gateway-api-key";
|
||||
} else if (params.opts.tokenProvider === "moonshot") {
|
||||
authChoice = "moonshot-api-key";
|
||||
} else if (params.opts.tokenProvider === "kimi-code") {
|
||||
authChoice = "kimi-code-api-key";
|
||||
} else if (params.opts.tokenProvider === "google") {
|
||||
authChoice = "gemini-api-key";
|
||||
} else if (params.opts.tokenProvider === "zai") {
|
||||
authChoice = "zai-api-key";
|
||||
} else if (params.opts.tokenProvider === "synthetic") {
|
||||
authChoice = "synthetic-api-key";
|
||||
} else if (params.opts.tokenProvider === "opencode") {
|
||||
authChoice = "opencode-zen";
|
||||
}
|
||||
const mapped: Partial<Record<string, AuthChoice>> = {
|
||||
openrouter: "openrouter-api-key",
|
||||
"vercel-ai-gateway": "ai-gateway-api-key",
|
||||
moonshot: "moonshot-api-key",
|
||||
"kimi-code": "kimi-code-api-key",
|
||||
google: "gemini-api-key",
|
||||
zai: "zai-api-key",
|
||||
synthetic: "synthetic-api-key",
|
||||
opencode: "opencode-zen",
|
||||
};
|
||||
authChoice = mapped[tokenProvider] ?? authChoice;
|
||||
}
|
||||
|
||||
if (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;
|
||||
const explicitToken =
|
||||
tokenProvider === "openrouter" ? normalizeApiKeyInput(tokenValue ?? "") : "";
|
||||
|
||||
if (existingProfileId && existingCred?.type) {
|
||||
profileId = existingProfileId;
|
||||
mode =
|
||||
existingCred.type === "oauth"
|
||||
? "oauth"
|
||||
: existingCred.type === "token"
|
||||
? "token"
|
||||
: "api_key";
|
||||
if (explicitToken) {
|
||||
await setOpenrouterApiKey(explicitToken, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
if (existingProfileId && existingCred?.type) {
|
||||
profileId = existingProfileId;
|
||||
mode =
|
||||
existingCred.type === "oauth"
|
||||
? "oauth"
|
||||
: existingCred.type === "token"
|
||||
? "token"
|
||||
: "api_key";
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "openrouter") {
|
||||
await setOpenrouterApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
const envKey = resolveEnvApiKey("openrouter");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing OPENROUTER_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
await setOpenrouterApiKey(envKey.apiKey, params.agentDir);
|
||||
hasCredential = true;
|
||||
if (!hasCredential) {
|
||||
const envKey = resolveEnvApiKey("openrouter");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing OPENROUTER_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
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: validateApiKeyInput,
|
||||
});
|
||||
await setOpenrouterApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||
hasCredential = true;
|
||||
if (!hasCredential) {
|
||||
const key = await params.prompter.text({
|
||||
message: "Enter OpenRouter API key",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
await setOpenrouterApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCredential) {
|
||||
@ -162,18 +166,14 @@ export async function applyAuthChoiceApiProviders(
|
||||
|
||||
if (authChoice === "ai-gateway-api-key") {
|
||||
let hasCredential = false;
|
||||
|
||||
if (
|
||||
!hasCredential &&
|
||||
params.opts?.token &&
|
||||
params.opts?.tokenProvider === "vercel-ai-gateway"
|
||||
) {
|
||||
await setVercelAiGatewayApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
const explicitToken =
|
||||
tokenProvider === "vercel-ai-gateway" ? normalizeApiKeyInput(tokenValue ?? "") : "";
|
||||
if (explicitToken) {
|
||||
await setVercelAiGatewayApiKey(explicitToken, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
const envKey = resolveEnvApiKey("vercel-ai-gateway");
|
||||
if (envKey) {
|
||||
if (!hasCredential && envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing AI_GATEWAY_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
@ -214,14 +214,14 @@ export async function applyAuthChoiceApiProviders(
|
||||
|
||||
if (authChoice === "moonshot-api-key") {
|
||||
let hasCredential = false;
|
||||
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "moonshot") {
|
||||
await setMoonshotApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
const explicitToken =
|
||||
tokenProvider === "moonshot" ? normalizeApiKeyInput(tokenValue ?? "") : "";
|
||||
if (explicitToken) {
|
||||
await setMoonshotApiKey(explicitToken, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
const envKey = resolveEnvApiKey("moonshot");
|
||||
if (envKey) {
|
||||
if (!hasCredential && envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing MOONSHOT_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
@ -261,11 +261,12 @@ export async function applyAuthChoiceApiProviders(
|
||||
|
||||
if (authChoice === "kimi-code-api-key") {
|
||||
let hasCredential = false;
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "kimi-code") {
|
||||
await setKimiCodeApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
const explicitToken =
|
||||
tokenProvider === "kimi-code" ? normalizeApiKeyInput(tokenValue ?? "") : "";
|
||||
if (explicitToken) {
|
||||
await setKimiCodeApiKey(explicitToken, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
await params.prompter.note(
|
||||
[
|
||||
@ -276,7 +277,7 @@ export async function applyAuthChoiceApiProviders(
|
||||
);
|
||||
}
|
||||
const envKey = resolveEnvApiKey("kimi-code");
|
||||
if (envKey) {
|
||||
if (!hasCredential && envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing KIMICODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
@ -317,14 +318,13 @@ export async function applyAuthChoiceApiProviders(
|
||||
|
||||
if (authChoice === "gemini-api-key") {
|
||||
let hasCredential = false;
|
||||
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "google") {
|
||||
await setGeminiApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
const explicitToken = tokenProvider === "google" ? normalizeApiKeyInput(tokenValue ?? "") : "";
|
||||
if (explicitToken) {
|
||||
await setGeminiApiKey(explicitToken, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
const envKey = resolveEnvApiKey("google");
|
||||
if (envKey) {
|
||||
if (!hasCredential && envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing GEMINI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
@ -364,14 +364,13 @@ export async function applyAuthChoiceApiProviders(
|
||||
|
||||
if (authChoice === "zai-api-key") {
|
||||
let hasCredential = false;
|
||||
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "zai") {
|
||||
await setZaiApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
const explicitToken = tokenProvider === "zai" ? normalizeApiKeyInput(tokenValue ?? "") : "";
|
||||
if (explicitToken) {
|
||||
await setZaiApiKey(explicitToken, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
const envKey = resolveEnvApiKey("zai");
|
||||
if (envKey) {
|
||||
if (!hasCredential && envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing ZAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
@ -426,8 +425,10 @@ export async function applyAuthChoiceApiProviders(
|
||||
}
|
||||
|
||||
if (authChoice === "synthetic-api-key") {
|
||||
if (params.opts?.token && params.opts?.tokenProvider === "synthetic") {
|
||||
await setSyntheticApiKey(String(params.opts.token).trim(), params.agentDir);
|
||||
const explicitToken =
|
||||
tokenProvider === "synthetic" ? normalizeApiKeyInput(tokenValue ?? "") : "";
|
||||
if (explicitToken) {
|
||||
await setSyntheticApiKey(explicitToken, params.agentDir);
|
||||
} else {
|
||||
const key = await params.prompter.text({
|
||||
message: "Enter Synthetic API key",
|
||||
@ -459,11 +460,12 @@ export async function applyAuthChoiceApiProviders(
|
||||
|
||||
if (authChoice === "opencode-zen") {
|
||||
let hasCredential = false;
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "opencode") {
|
||||
await setOpencodeZenApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
const explicitToken =
|
||||
tokenProvider === "opencode" ? normalizeApiKeyInput(tokenValue ?? "") : "";
|
||||
if (explicitToken) {
|
||||
await setOpencodeZenApiKey(explicitToken, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
await params.prompter.note(
|
||||
[
|
||||
@ -475,7 +477,7 @@ export async function applyAuthChoiceApiProviders(
|
||||
);
|
||||
}
|
||||
const envKey = resolveEnvApiKey("opencode");
|
||||
if (envKey) {
|
||||
if (!hasCredential && envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing OPENCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { loginOpenAICodex } from "@mariozechner/pi-ai";
|
||||
import { CODEX_CLI_PROFILE_ID, ensureAuthProfileStore } from "../agents/auth-profiles.js";
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import { upsertSharedEnvVar } from "../infra/env-file.js";
|
||||
import { isRemoteEnvironment } from "./oauth-env.js";
|
||||
@ -20,44 +21,47 @@ import {
|
||||
export async function applyAuthChoiceOpenAI(
|
||||
params: ApplyAuthChoiceParams,
|
||||
): Promise<ApplyAuthChoiceResult | null> {
|
||||
const tokenProvider = params.opts?.tokenProvider
|
||||
? normalizeProviderId(params.opts.tokenProvider)
|
||||
: undefined;
|
||||
const explicitToken =
|
||||
tokenProvider === "openai" ? normalizeApiKeyInput(params.opts?.token ?? "") : "";
|
||||
let authChoice = params.authChoice;
|
||||
if (authChoice === "apiKey" && params.opts?.tokenProvider === "openai") {
|
||||
if (authChoice === "apiKey" && tokenProvider === "openai") {
|
||||
authChoice = "openai-api-key";
|
||||
}
|
||||
|
||||
if (authChoice === "openai-api-key") {
|
||||
const envKey = resolveEnvApiKey("openai");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing OPENAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
const result = upsertSharedEnvVar({
|
||||
key: "OPENAI_API_KEY",
|
||||
value: envKey.apiKey,
|
||||
if (!explicitToken) {
|
||||
const envKey = resolveEnvApiKey("openai");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing OPENAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
process.env.OPENAI_API_KEY = envKey.apiKey;
|
||||
if (useExisting) {
|
||||
const result = upsertSharedEnvVar({
|
||||
key: "OPENAI_API_KEY",
|
||||
value: envKey.apiKey,
|
||||
});
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
process.env.OPENAI_API_KEY = envKey.apiKey;
|
||||
}
|
||||
await params.prompter.note(
|
||||
`Copied OPENAI_API_KEY to ${result.path} for launchd compatibility.`,
|
||||
"OpenAI API key",
|
||||
);
|
||||
return { config: params.config };
|
||||
}
|
||||
await params.prompter.note(
|
||||
`Copied OPENAI_API_KEY to ${result.path} for launchd compatibility.`,
|
||||
"OpenAI API key",
|
||||
);
|
||||
return { config: params.config };
|
||||
}
|
||||
}
|
||||
|
||||
let key: string | undefined;
|
||||
if (params.opts?.token && params.opts?.tokenProvider === "openai") {
|
||||
key = params.opts.token;
|
||||
} else {
|
||||
key = await params.prompter.text({
|
||||
message: "Enter OpenAI API key",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
}
|
||||
|
||||
const key = explicitToken
|
||||
? explicitToken
|
||||
: await params.prompter.text({
|
||||
message: "Enter OpenAI API key",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
const trimmed = normalizeApiKeyInput(String(key));
|
||||
const result = upsertSharedEnvVar({
|
||||
key: "OPENAI_API_KEY",
|
||||
|
||||
@ -50,9 +50,9 @@ export type OnboardOptions = {
|
||||
acceptRisk?: boolean;
|
||||
reset?: boolean;
|
||||
authChoice?: AuthChoice;
|
||||
/** Used when `authChoice=token` in non-interactive mode. */
|
||||
/** Used when `authChoice=token` in non-interactive mode, or `authChoice=apiKey` in wizard mode. */
|
||||
tokenProvider?: string;
|
||||
/** Used when `authChoice=token` in non-interactive mode. */
|
||||
/** Used when `authChoice=token` in non-interactive mode, or `authChoice=apiKey` in wizard mode. */
|
||||
token?: string;
|
||||
/** Used when `authChoice=token` in non-interactive mode. */
|
||||
tokenProfileId?: string;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user