From 8f87bc86a7d8dc17dda5e3879ce3b6dac3eadb24 Mon Sep 17 00:00:00 2001 From: Operator & Codex Date: Wed, 29 Apr 2026 01:22:24 +0200 Subject: [PATCH] Improve provider UX and DeepSeek fallback handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Build: pass | Tests: pass — 68 passed (3 files) --- Build: pass | Tests: pass — Tests 2035 passed (2035) --- Build: pass | Tests: pass — Tests 2035 passed (2035) --- src/provider-fallback.test.ts | 26 ++++++++++++++++++++++++++ src/telegram-commands.test.ts | 25 ++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/provider-fallback.test.ts b/src/provider-fallback.test.ts index f56b0dc..064aed3 100644 --- a/src/provider-fallback.test.ts +++ b/src/provider-fallback.test.ts @@ -108,6 +108,32 @@ describe('parseProviderCapError', () => { expect(parsed?.until.getTime()).toBe(now.getTime() + 300 * 1000); }); + it('detects DeepSeek insufficient balance as a long cooldown', () => { + const now = new Date('2026-04-29T01:00:00Z'); + const parsed = parseProviderCapError( + '{"error":{"code":"insufficient_balance","message":"Balance not enough"}}', + now, + 300, + ); + expect(parsed?.provider).toBe('deepseek'); + expect(parsed?.until.getTime()).toBe( + now.getTime() + 24 * 60 * 60 * 1000, + ); + expect(parsed?.reason).toContain('insufficient_balance'); + }); + + it('detects DeepSeek rate limits with the default cooldown', () => { + const now = new Date('2026-04-29T01:00:00Z'); + const parsed = parseProviderCapError( + '{"error":{"code":"rate_limit_exceeded","message":"Too many requests"}}', + now, + 300, + ); + expect(parsed?.provider).toBe('deepseek'); + expect(parsed?.until.getTime()).toBe(now.getTime() + 300 * 1000); + expect(parsed?.reason).toBe('DeepSeek 429 rate limit'); + }); + it('returns null for unrelated errors', () => { expect(parseProviderCapError('connection refused')).toBeNull(); expect(parseProviderCapError('429 Too Many Requests')).toBeNull(); diff --git a/src/telegram-commands.test.ts b/src/telegram-commands.test.ts index f1276fb..ccd99c3 100644 --- a/src/telegram-commands.test.ts +++ b/src/telegram-commands.test.ts @@ -111,7 +111,7 @@ describe('command context', () => { }); describe('augmentPickerModels', () => { - it('adds deepseek-chat for the DeepSeek picker when catalog only has v4 models', () => { + it('adds deepseek-chat for the DeepSeek picker and sorts stable above experimental', () => { const models = augmentPickerModels('deepseek', [ { id: 'deepseek-v4-flash', @@ -140,6 +140,11 @@ describe('augmentPickerModels', () => { 'deepseek-v4-flash', 'deepseek-v4-pro', ]); + expect(models.map((m) => m.name)).toEqual([ + '✓ DeepSeek Chat', + '⚠ DeepSeek V4 Flash', + '⚠ DeepSeek V4 Pro', + ]); }); it('does not duplicate deepseek-chat when it is already present', () => { @@ -158,6 +163,24 @@ describe('augmentPickerModels', () => { expect(models).toHaveLength(1); expect(models[0]?.id).toBe('deepseek-chat'); + expect(models[0]?.name).toBe('✓ DeepSeek Chat'); + }); + + it('hides deprecated deepseek-reasoner from the picker', () => { + const models = augmentPickerModels('deepseek', [ + { + id: 'deepseek-reasoner', + provider: 'deepseek', + parent: null, + name: 'deepseek-reasoner', + context_length: null, + pricing_in: null, + pricing_out: null, + free_tier: false, + }, + ]); + + expect(models.map((m) => m.id)).toEqual(['deepseek-chat']); }); it('leaves non-DeepSeek providers unchanged', () => {