mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
fix: surface provider auth error for unavailable models (#1506)
* fix: surface provider auth error for unavailable models When an OAuth-managed model returns 401 after a forced token refresh, the token is valid but the provider rejected it for that model (the account lacks access). Emit provider.auth_error carrying the provider's message instead of auth.login_required with a misleading "OAuth login expired. Send /login" prompt. * fix(agent-core): preserve provider auth errors through compaction Treat provider.auth_error like auth.login_required in the compaction path so an auth rejection during compaction surfaces the provider's message instead of being wrapped as a generic compaction failure.
This commit is contained in:
parent
2394d013bc
commit
e83511a711
5 changed files with 22 additions and 10 deletions
5
.changeset/fix-model-auth-error-message.md
Normal file
5
.changeset/fix-model-auth-error-message.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Fix a misleading "OAuth login expired" message shown when a model is not available for the current account; the CLI now shows the provider's actual error.
|
||||
|
|
@ -595,7 +595,12 @@ export class FullCompaction {
|
|||
thinking_effort: this.agent.config.thinkingEffort,
|
||||
error_type: error instanceof Error ? error.name : 'Unknown',
|
||||
});
|
||||
if (isKimiError(error) && error.code === ErrorCodes.AUTH_LOGIN_REQUIRED) throw error;
|
||||
if (
|
||||
isKimiError(error) &&
|
||||
(error.code === ErrorCodes.AUTH_LOGIN_REQUIRED ||
|
||||
error.code === ErrorCodes.PROVIDER_AUTH_ERROR)
|
||||
)
|
||||
throw error;
|
||||
throw new KimiError(ErrorCodes.COMPACTION_FAILED, String(error), { cause: error });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,9 +203,10 @@ export class ProviderManager implements ModelProvider {
|
|||
} catch (error) {
|
||||
if (!(error instanceof APIStatusError) || error.statusCode !== 401) throw error;
|
||||
if (refreshed) {
|
||||
const reason = error.message.replaceAll('\r', '');
|
||||
throw new KimiError(
|
||||
ErrorCodes.AUTH_LOGIN_REQUIRED,
|
||||
'OAuth provider credentials were rejected. Send /login to login.',
|
||||
ErrorCodes.PROVIDER_AUTH_ERROR,
|
||||
reason.length > 0 ? reason : 'OAuth provider credentials were rejected.',
|
||||
{
|
||||
cause: error,
|
||||
details: { statusCode: error.statusCode, requestId: error.requestId },
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ describe('FullCompaction', () => {
|
|||
expect(messageText(compactionCall?.history[5])).toBe('lookup result');
|
||||
});
|
||||
|
||||
it('force-refreshes OAuth credentials on compaction 401 and falls back to login_required when replay 401', async () => {
|
||||
it('force-refreshes OAuth credentials on compaction 401 and treats replay 401 as provider auth error', async () => {
|
||||
const tokenCalls: Array<boolean | undefined> = [];
|
||||
const authKeys: string[] = [];
|
||||
const oauthOptions = oauthTestAgentOptions(async (options) => {
|
||||
|
|
@ -398,7 +398,7 @@ describe('FullCompaction', () => {
|
|||
expect.objectContaining({
|
||||
event: 'error',
|
||||
args: expect.objectContaining({
|
||||
code: 'auth.login_required',
|
||||
code: 'provider.auth_error',
|
||||
details: expect.objectContaining({
|
||||
statusCode: 401,
|
||||
requestId: 'req-compact-401',
|
||||
|
|
|
|||
|
|
@ -1409,7 +1409,7 @@ describe('Agent turn flow', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('falls back to login_required when force-refresh and replay both 401', async () => {
|
||||
it('treats 401 after force-refresh as provider auth error', async () => {
|
||||
const tokenCalls: Array<boolean | undefined> = [];
|
||||
const authKeys: string[] = [];
|
||||
const oauthOptions = oauthAgentOptions(
|
||||
|
|
@ -1447,7 +1447,7 @@ describe('Agent turn flow', () => {
|
|||
args: expect.objectContaining({
|
||||
reason: 'failed',
|
||||
error: expect.objectContaining({
|
||||
code: 'auth.login_required',
|
||||
code: 'provider.auth_error',
|
||||
details: expect.objectContaining({
|
||||
statusCode: 401,
|
||||
requestId: 'req-401',
|
||||
|
|
@ -1644,7 +1644,7 @@ describe('Agent turn flow', () => {
|
|||
expect(payloads[1]).toMatchObject({ turnStep: '0.1', attempt: '2/3' });
|
||||
});
|
||||
|
||||
it('force-refreshes OAuth credentials on video upload 401 and falls back to login_required when replay 401', async () => {
|
||||
it('force-refreshes OAuth credentials on video upload 401 and surfaces the provider auth error when replay 401', async () => {
|
||||
const tokenCalls: Array<boolean | undefined> = [];
|
||||
const authKeys: string[] = [];
|
||||
const oauthOptions = oauthAgentOptions(
|
||||
|
|
@ -1689,8 +1689,9 @@ describe('Agent turn flow', () => {
|
|||
expect(result.isError).toBe(true);
|
||||
expect(authKeys).toEqual(['fresh-token', 'forced-refresh-token']);
|
||||
expect(tokenCalls).toEqual([undefined, true]);
|
||||
expect(result.output).toContain('OAuth provider credentials were rejected');
|
||||
expect(result.output).toContain('Send /login to login');
|
||||
expect(result.output).toContain('Unauthorized');
|
||||
expect(result.output).not.toContain('OAuth provider credentials were rejected');
|
||||
expect(result.output).not.toContain('Send /login to login');
|
||||
});
|
||||
|
||||
it('cancels an active turn', async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue