Fix Z.AI credential resolution and improve 401 auth diagnostics

This commit is contained in:
William Valentin
2026-02-15 19:47:27 -08:00
parent 81c97a9df1
commit dd15ccb927
5 changed files with 83 additions and 23 deletions
+17
View File
@@ -135,4 +135,21 @@ describe('OpenAIClient tool use', () => {
expect(response.stopReason).toBe('max_tokens');
});
it('rewrites Z.AI 401 errors with actionable auth guidance', async () => {
mockCreate.mockRejectedValueOnce({
status: 401,
message: '401 You have insufficient permissions for this operation. Missing scopes: model.request.',
});
const client = new OpenAIClient({
apiKey: 'zai-key',
model: 'glm-4.7',
baseURL: 'https://api.z.ai/api/paas/v4',
});
await expect(client.chat({
messages: [{ role: 'user', content: 'hello' }],
})).rejects.toThrow(/Z\.AI authentication failed \(401\)/);
});
});
+26 -1
View File
@@ -61,10 +61,12 @@ export class OpenAIClient implements ModelClient {
private model: string;
private defaultMaxTokens: number;
private useOAuth: boolean;
private baseURL?: string;
constructor(config: OpenAIClientConfig) {
const timeoutMs = config.timeoutMs ?? 20_000;
this.useOAuth = Boolean(config.useOAuth);
this.baseURL = config.baseURL;
// OAuth mode uses a different backend (ChatGPT Codex) and a different API shape.
// Only initialize the OpenAI SDK for API-key providers.
@@ -248,7 +250,30 @@ export class OpenAIClient implements ModelClient {
(params as any).reasoning_effort = 'medium';
}
const response = await this.client.chat.completions.create(params);
let response: OpenAI.ChatCompletion;
try {
response = await this.client.chat.completions.create(params);
} catch (error) {
const status = typeof (error as { status?: unknown })?.status === 'number'
? (error as { status: number }).status
: undefined;
const message = error instanceof Error ? error.message : String(error);
const isZai = (this.baseURL ?? '').includes('api.z.ai');
const missingModelRequestScope = message.includes('Missing scopes: model.request');
if (isZai && status === 401) {
const hint = missingModelRequestScope
? 'The key lacks `model.request` scope.'
: 'The API key is invalid, expired, or not allowed for this model/endpoint.';
throw new Error(
`Z.AI authentication failed (401). ${hint} ` +
'Run `flynn zai-auth` to update credentials, or set ZAI_API_KEY / ZHIPUAI_API_KEY / ZHIPUAI_AUTH_TOKEN.',
);
}
throw error;
}
const choice = response.choices[0];
const content = choice?.message?.content ?? '';