From e4fddabb19303e51b70c17a898b606b6035d6f59 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Mon, 3 Aug 2026 18:42:31 +0300 Subject: [PATCH] fix(quota): normalize DeepSeek timeout errors --- packages/vscode/src/quotaProviders.test.ts | 21 +++++++++++++++++++ packages/vscode/src/quotaProviders.ts | 4 +++- .../server/lib/quota/providers/deepseek.js | 4 +++- .../lib/quota/providers/deepseek.test.js | 9 ++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/vscode/src/quotaProviders.test.ts b/packages/vscode/src/quotaProviders.test.ts index 7d00df14..94451eac 100644 --- a/packages/vscode/src/quotaProviders.test.ts +++ b/packages/vscode/src/quotaProviders.test.ts @@ -422,6 +422,12 @@ describe('NeuralWatt quota provider (VS Code parity)', () => { }); describe('DeepSeek quota provider (VS Code parity)', () => { + beforeEach(() => { + const fsMock = fs as unknown as { existsSync: () => boolean; readFileSync: () => string }; + fsMock.existsSync = () => true; + fsMock.readFileSync = () => AUTH; + }); + test('builds credits_balance window from documented USD payload (string balance)', async () => { stubFetchReturning(() => Promise.resolve(mockResponse({ is_available: true, @@ -463,6 +469,15 @@ describe('DeepSeek quota provider (VS Code parity)', () => { assert.equal(result.error, 'Session expired — please re-authenticate with DeepSeek'); }); + test('reports a normalized timeout error', async () => { + stubFetchReturning(() => Promise.reject(new DOMException('The operation timed out.', 'TimeoutError'))); + + const result = await fetchQuotaForProvider('deepseek'); + + assert.equal(result.ok, false); + assert.equal(result.error, 'Request timed out'); + }); + test('returns no-quota-data on a 200 payload with no usable balance', async () => { stubFetchReturning(() => Promise.resolve(mockResponse({ is_available: true, @@ -488,4 +503,10 @@ describe('DeepSeek quota provider (VS Code parity)', () => { assert.equal(result.ok, true); assert.equal(result.usage!.windows.credits_balance!.valueLabel, '$0.00'); }); + + test('teardown: restore fs', () => { + const fsMock = fs as unknown as { existsSync: unknown; readFileSync: unknown }; + fsMock.existsSync = ORIGINAL_FS.existsSync; + fsMock.readFileSync = ORIGINAL_FS.readFileSync; + }); }); diff --git a/packages/vscode/src/quotaProviders.ts b/packages/vscode/src/quotaProviders.ts index b275f593..f759e351 100644 --- a/packages/vscode/src/quotaProviders.ts +++ b/packages/vscode/src/quotaProviders.ts @@ -2285,7 +2285,9 @@ const fetchDeepseekQuota = async (): Promise => { usage: { windows }, }); } catch (error) { - const isTimeout = error instanceof DOMException && error.name === 'AbortError' && timeoutSignal.aborted; + const isTimeout = error instanceof DOMException && ( + error.name === 'TimeoutError' || (error.name === 'AbortError' && timeoutSignal.aborted) + ); const isParseError = error instanceof SyntaxError; return buildResult({ providerId: 'deepseek', diff --git a/packages/web/server/lib/quota/providers/deepseek.js b/packages/web/server/lib/quota/providers/deepseek.js index 3067783b..8963ca82 100644 --- a/packages/web/server/lib/quota/providers/deepseek.js +++ b/packages/web/server/lib/quota/providers/deepseek.js @@ -99,7 +99,9 @@ export const fetchQuota = async () => { usage: { windows } }); } catch (error) { - const isTimeout = error instanceof DOMException && error.name === 'AbortError' && timeoutSignal.aborted; + const isTimeout = error instanceof DOMException && ( + error.name === 'TimeoutError' || (error.name === 'AbortError' && timeoutSignal.aborted) + ); const isParseError = error instanceof SyntaxError; return buildResult({ providerId, diff --git a/packages/web/server/lib/quota/providers/deepseek.test.js b/packages/web/server/lib/quota/providers/deepseek.test.js index d92728c5..a133bb6d 100644 --- a/packages/web/server/lib/quota/providers/deepseek.test.js +++ b/packages/web/server/lib/quota/providers/deepseek.test.js @@ -119,6 +119,15 @@ describe('DeepSeek quota provider', () => { expect(result.error).toBe('Invalid response from provider'); }); + it('reports a normalized timeout error', async () => { + vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new DOMException('The operation timed out.', 'TimeoutError'))); + + const result = await fetchQuota(); + + expect(result.ok).toBe(false); + expect(result.error).toBe('Request timed out'); + }); + it('returns no-quota-data on a 200 payload with no usable balance', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse({ is_available: true,