diff --git a/src/vs/workbench/contrib/chat/common/chatErrorMessages.ts b/src/vs/workbench/contrib/chat/common/chatErrorMessages.ts index b2733963facb9..87bd4366fc91d 100644 --- a/src/vs/workbench/contrib/chat/common/chatErrorMessages.ts +++ b/src/vs/workbench/contrib/chat/common/chatErrorMessages.ts @@ -354,7 +354,19 @@ function isForwardedChatError(value: unknown): value is IForwardedChatError { */ export function getChatErrorDetailsFromMeta(error: ErrorInfo | undefined, context?: IChatErrorContext): IChatResponseErrorDetails | undefined { const meta = error?._meta; - const chatError = meta?.chatError; + let chatError = meta?.chatError; + // In some hosts the forwarded chat error may be JSON-stringified. Accept + // either the raw object or a stringified payload and try to parse it so we + // can render friendly messages instead of falling back to generic errors. + if (typeof chatError === 'string') { + try { + chatError = JSON.parse(chatError); + } catch { + // If parsing fails, fall back to undefined so callers use their + // existing generic handling. + return undefined; + } + } if (!isForwardedChatError(chatError)) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.malformed.test.ts b/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.malformed.test.ts new file mode 100644 index 0000000000000..a3e25930b6027 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.malformed.test.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { getChatErrorDetailsFromMeta } from '../../common/chatErrorMessages.js'; +import type { ErrorInfo } from '../../../../../platform/agentHost/common/state/protocol/state.js'; + +function errorInfo(meta: Record | undefined): ErrorInfo { + return { errorType: 'e', message: 'm', _meta: meta }; +} + +suite('ChatErrorMessages (malformed stringified payload)', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('invalid JSON in forwarded chatError returns undefined (safe fallback)', () => { + // Malformed JSON string should not throw; the helper should return undefined + // so callers fall back to generic error handling. + const payload = '{ fetchError: { type: "rateLimited", retryAfter: 60 }'; // missing closing brace + const details = getChatErrorDetailsFromMeta(errorInfo({ chatError: payload })); + assert.strictEqual(details, undefined); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.test.ts b/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.test.ts new file mode 100644 index 0000000000000..a44998464ee4b --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.test.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { ChatFetchResponseType, getChatErrorDetailsFromMeta } from '../../common/chatErrorMessages.js'; +import { ChatErrorLevel } from '../../common/chatService/chatService.js'; +import type { ErrorInfo } from '../../../../../platform/agentHost/common/state/protocol/state.js'; + +function errorInfo(meta: Record | undefined): ErrorInfo { + return { errorType: 'e', message: 'm', _meta: meta }; +} + +suite('ChatErrorMessages (stringified payload)', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('parses stringified forwarded chatError', () => { + const payload = JSON.stringify({ + fetchError: { + type: ChatFetchResponseType.RateLimited, + retryAfter: 60, + capiError: { code: 'user_global_rate_limited', message: 'slow down' }, + }, + copilotPlan: 'free', + }); + + const details = getChatErrorDetailsFromMeta(errorInfo({ chatError: payload })); + assert.deepStrictEqual(details, { + code: ChatFetchResponseType.RateLimited, + message: 'You\'ve hit your session rate limit. Please upgrade your plan or wait 60 seconds for your limit to reset. [Learn More](https://aka.ms/github-copilot-rate-limit-error)', + level: ChatErrorLevel.Info, + isRateLimited: true, + }); + }); +});