Add API vs Coding Plan mode selection for Z.AI auth

This commit is contained in:
William Valentin
2026-02-15 20:06:35 -08:00
parent 6a31ee2885
commit 99ad53a1ee
4 changed files with 84 additions and 4 deletions
+21 -1
View File
@@ -60,7 +60,7 @@ describe('zai-auth command', () => {
it('re-prompts and stores new key when credential exists and user answers yes', async () => {
mockLoadStoredZaiAuth.mockReturnValue({ api_key: 'existing-key', created_at: '2026-02-16T00:00:00.000Z' });
mockReadlineAnswers(['y', 'new-zai-key']);
mockReadlineAnswers(['y', '1', 'new-zai-key']);
const program = new Command();
const { registerZaiAuthCommand } = await import('./zai-auth.js');
@@ -77,4 +77,24 @@ describe('zai-auth command', () => {
consoleLog.mockRestore();
consoleError.mockRestore();
});
it('supports --mode plan without interactive mode selection', async () => {
mockLoadStoredZaiAuth.mockReturnValue(null);
mockReadlineAnswers(['new-zai-plan-key']);
const program = new Command();
const { registerZaiAuthCommand } = await import('./zai-auth.js');
registerZaiAuthCommand(program);
const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined);
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
await program.parseAsync(['node', 'test', 'zai-auth', '--mode', 'plan']);
expect(mockStoreZaiAuth).toHaveBeenCalledWith('new-zai-plan-key');
expect(consoleError).not.toHaveBeenCalled();
consoleLog.mockRestore();
consoleError.mockRestore();
});
});
+40 -2
View File
@@ -2,6 +2,8 @@ import type { Command } from 'commander';
import readline from 'readline';
import { loadStoredZaiAuth, storeZaiAuth } from '../auth/index.js';
type ZaiAuthMode = 'api' | 'plan';
async function promptHidden(question: string): Promise<string> {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
const rlAny = rl as unknown as { stdoutMuted?: boolean; _writeToOutput?: (s: string) => void };
@@ -35,11 +37,39 @@ async function promptYesNo(question: string): Promise<boolean> {
return normalized === 'y' || normalized === 'yes';
}
async function promptText(question: string): Promise<string> {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
const answer = await new Promise<string>((resolve) => rl.question(question, resolve));
rl.close();
return answer.trim();
}
function parseZaiAuthMode(value: string): ZaiAuthMode {
const mode = value.trim().toLowerCase();
if (mode === 'api' || mode === 'plan') {
return mode;
}
throw new Error(`Invalid mode "${value}". Expected: api or plan.`);
}
async function resolveZaiAuthMode(mode?: ZaiAuthMode): Promise<ZaiAuthMode> {
if (mode) {
return mode;
}
console.log('Choose Z.AI auth mode:');
console.log(' 1) API (standard API endpoint)');
console.log(' 2) Coding Plan (coding endpoint)');
const choice = (await promptText('Select [1-2] (default 1): ')).toLowerCase();
return choice === '2' || choice === 'plan' ? 'plan' : 'api';
}
export function registerZaiAuthCommand(program: Command): void {
program
.command('zai-auth')
.description('Store a Z.AI API key for the zhipuai provider (auth.json)')
.action(async () => {
.option('--mode <mode>', 'Credential mode: api or plan', parseZaiAuthMode)
.action(async (opts: { mode?: ZaiAuthMode }) => {
const existing = loadStoredZaiAuth();
if (existing) {
console.log('Z.AI credential already exists.');
@@ -50,6 +80,8 @@ export function registerZaiAuthCommand(program: Command): void {
}
}
const mode = await resolveZaiAuthMode(opts.mode);
console.log('Z.AI uses API keys (HTTP Bearer), not an OAuth device flow.');
console.log('Create a key at: https://z.ai/manage-apikey/apikey-list');
console.log('');
@@ -60,7 +92,13 @@ export function registerZaiAuthCommand(program: Command): void {
console.log('');
console.log('Z.AI credential stored in ~/.config/flynn/auth.json');
console.log('');
console.log('Tip: For GLM Coding Plan set model endpoint to: https://api.z.ai/api/coding/paas/v4');
if (mode === 'plan') {
console.log('Mode: Coding Plan');
console.log('Set model endpoint to: https://api.z.ai/api/coding/paas/v4');
} else {
console.log('Mode: API');
console.log('Set model endpoint to: https://api.z.ai/api/paas/v4');
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`Z.AI auth failed: ${message}`);