Handle Z.AI textual 401 errors for auth diagnostics
This commit is contained in:
@@ -47,14 +47,14 @@
|
|||||||
"status": "completed",
|
"status": "completed",
|
||||||
"date": "2026-02-16",
|
"date": "2026-02-16",
|
||||||
"updated": "2026-02-16",
|
"updated": "2026-02-16",
|
||||||
"summary": "Unified Z.AI credential resolution so zhipuai model switches resolve credentials from config, env (including ZAI_API_KEY), and auth store regardless of use_oauth flag. Added clearer Z.AI-specific 401 diagnostics when API auth fails (including missing model.request scope hint).",
|
"summary": "Unified Z.AI credential resolution so zhipuai model switches resolve credentials from config, env (including ZAI_API_KEY), and auth store regardless of use_oauth flag. Added clearer Z.AI-specific 401 diagnostics when API auth fails (including missing model.request scope hint), including textual 401 errors where the SDK does not expose status.",
|
||||||
"files_modified": [
|
"files_modified": [
|
||||||
"src/daemon/models.ts",
|
"src/daemon/models.ts",
|
||||||
"src/models/openai.ts",
|
"src/models/openai.ts",
|
||||||
"src/daemon/clientFactory.test.ts",
|
"src/daemon/clientFactory.test.ts",
|
||||||
"src/models/openai.test.ts"
|
"src/models/openai.test.ts"
|
||||||
],
|
],
|
||||||
"test_status": "pnpm test:run src/daemon/clientFactory.test.ts src/models/openai.test.ts + pnpm typecheck passing"
|
"test_status": "pnpm test:run src/daemon/clientFactory.test.ts src/models/openai.test.ts + pnpm typecheck passing (updated to cover textual 401 without status field)"
|
||||||
},
|
},
|
||||||
|
|
||||||
"deployment-port-env-override": {
|
"deployment-port-env-override": {
|
||||||
|
|||||||
@@ -152,4 +152,20 @@ describe('OpenAIClient tool use', () => {
|
|||||||
messages: [{ role: 'user', content: 'hello' }],
|
messages: [{ role: 'user', content: 'hello' }],
|
||||||
})).rejects.toThrow(/Z\.AI authentication failed \(401\)/);
|
})).rejects.toThrow(/Z\.AI authentication failed \(401\)/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('rewrites Z.AI textual 401 errors when SDK status is absent', async () => {
|
||||||
|
mockCreate.mockRejectedValueOnce(new Error(
|
||||||
|
'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(/The key lacks `model\.request` scope/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -260,9 +260,10 @@ export class OpenAIClient implements ModelClient {
|
|||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
const isZai = (this.baseURL ?? '').includes('api.z.ai');
|
const isZai = (this.baseURL ?? '').includes('api.z.ai');
|
||||||
|
const isUnauthorized401 = status === 401 || /\b401\b/.test(message);
|
||||||
const missingModelRequestScope = message.includes('Missing scopes: model.request');
|
const missingModelRequestScope = message.includes('Missing scopes: model.request');
|
||||||
|
|
||||||
if (isZai && status === 401) {
|
if (isZai && isUnauthorized401) {
|
||||||
const hint = missingModelRequestScope
|
const hint = missingModelRequestScope
|
||||||
? 'The key lacks `model.request` scope.'
|
? 'The key lacks `model.request` scope.'
|
||||||
: 'The API key is invalid, expired, or not allowed for this model/endpoint.';
|
: 'The API key is invalid, expired, or not allowed for this model/endpoint.';
|
||||||
|
|||||||
Reference in New Issue
Block a user