From 7041bc5f6c812bb49493da9e5dfa2c677cdcccc8 Mon Sep 17 00:00:00 2001 From: MackDing Date: Fri, 1 May 2026 16:47:03 +0800 Subject: [PATCH] fix: enable deepseek thinking options on compat routes --- src/core/agent.ts | 6 ++-- src/utils/provider-options.test.ts | 47 ++++++++++++++++++++++++++++++ src/utils/provider-options.ts | 27 +++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 src/utils/provider-options.test.ts create mode 100644 src/utils/provider-options.ts diff --git a/src/core/agent.ts b/src/core/agent.ts index 70a93608..0e95520b 100644 --- a/src/core/agent.ts +++ b/src/core/agent.ts @@ -9,13 +9,13 @@ import type { MercuryConfig } from '../utils/config.js'; import type { TokenBudget } from '../utils/tokens.js'; import type { CapabilityRegistry } from '../capabilities/registry.js'; import type { ScheduledTaskManifest } from './scheduler.js'; -import { DeepSeekProvider } from '../providers/deepseek.js'; import { Lifecycle } from './lifecycle.js'; import { Scheduler } from './scheduler.js'; import { logger } from '../utils/logger.js'; import { CLIChannel } from '../channels/cli.js'; import { TelegramChannel } from '../channels/telegram.js'; import { formatToolStep } from '../utils/tool-label.js'; +import { getReasoningProviderOptions } from '../utils/provider-options.js'; import type { ArrowSelectOption } from '../utils/arrow-select.js'; import { approveTelegramPendingRequest, @@ -516,9 +516,7 @@ export class Agent { for (const provider of fallbackIterator) { try { - const deepseekProviderOptions = provider instanceof DeepSeekProvider && provider.isReasoner - ? { deepseek: { thinking: { type: 'enabled' as const } } } - : undefined; + const deepseekProviderOptions = getReasoningProviderOptions(provider); logger.info({ provider: provider.name, model: provider.getModel(), steps: MAX_STEPS, stream: canStream }, 'Generating agentic response'); diff --git a/src/utils/provider-options.test.ts b/src/utils/provider-options.test.ts new file mode 100644 index 00000000..db04200c --- /dev/null +++ b/src/utils/provider-options.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest'; +import { getReasoningProviderOptions } from './provider-options.js'; +import { BaseProvider } from '../providers/base.js'; + +class FakeProvider extends BaseProvider { + readonly name: string; + readonly model: string; + + constructor(name: string, model: string, baseUrl: string = '') { + super({ name, model, baseUrl, apiKey: 'x', enabled: true }); + this.name = name; + this.model = model; + } + + async generateText(): Promise { + throw new Error('not used'); + } + + async *streamText(): AsyncIterable { + throw new Error('not used'); + } + + isAvailable(): boolean { + return true; + } + + getModelInstance(): any { + return null; + } +} + +describe('getReasoningProviderOptions', () => { + it('enables thinking for deepseek reasoning models over openai-compat routes', () => { + const provider = new FakeProvider('openaiCompat', 'deepseek-r1', 'https://api.deepseek.com/v1'); + expect(getReasoningProviderOptions(provider)).toEqual({ deepseek: { thinking: { type: 'enabled' } } }); + }); + + it('does not enable thinking for non-deepseek routes even if model name is similar', () => { + const provider = new FakeProvider('openaiCompat', 'deepseek-r1', 'https://example.com/v1'); + expect(getReasoningProviderOptions(provider)).toBeUndefined(); + }); + + it('does not enable thinking for normal deepseek chat models', () => { + const provider = new FakeProvider('deepseek', 'deepseek-chat', 'https://api.deepseek.com/v1'); + expect(getReasoningProviderOptions(provider)).toBeUndefined(); + }); +}); diff --git a/src/utils/provider-options.ts b/src/utils/provider-options.ts new file mode 100644 index 00000000..d9cc7e60 --- /dev/null +++ b/src/utils/provider-options.ts @@ -0,0 +1,27 @@ +import type { BaseProvider } from '../providers/base.js'; +import { DeepSeekProvider } from '../providers/deepseek.js'; + +export function getReasoningProviderOptions(provider: BaseProvider): { deepseek: { thinking: { type: 'enabled' } } } | undefined { + const model = (provider.getModel?.() || '').toLowerCase(); + const name = (provider.name || '').toLowerCase(); + const baseUrl = ((provider as any).config?.baseUrl || '').toLowerCase(); + + if (provider instanceof DeepSeekProvider && provider.isReasoner) { + return { deepseek: { thinking: { type: 'enabled' } } }; + } + + const looksLikeDeepSeekReasoningModel = + model.includes('deepseek-reasoner') || + model.includes('deepseek-r1') || + model.includes('deepseek-v3.1'); + + const routedToDeepSeek = + name === 'deepseek' || + baseUrl.includes('deepseek.com'); + + if (looksLikeDeepSeekReasoningModel && routedToDeepSeek) { + return { deepseek: { thinking: { type: 'enabled' } } }; + } + + return undefined; +}