From 440dedd9c15909fb3983f1ee8980a2c91d61ed7d Mon Sep 17 00:00:00 2001 From: Krzysztof Marciniak <67335619+Klakocik@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:36:50 +0200 Subject: [PATCH] feat: update DeepSeek models to V4 Flash and V4 Pro (#103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: update DeepSeek models to V4 Flash and V4 Pro - Replace deprecated deepseek-chat / deepseek-reasoner with deepseek-v4-flash / deepseek-v4-pro - Update base URL from api.deepseek.com/v1 to api.deepseek.com - Update context window (1M) and max output (384K) per official API docs - Old models deprecated 2026-07-24 * fix: resolveGeneralLLMConfig missing baseUrl + Admiral timeout 180s resolveGeneralLLMConfig constructed a new LLMConfig but omitted baseUrl, causing the adapter to fall back to api.openai.com/v1 regardless of provider — DeepSeek keys hit OpenAI (401). Also raised Admiral preview timeout to 180s for thinking-mode models. * fix: createTempestCommandInstance missing baseUrl for operator exec Operator LLM calls also lacked baseUrl, same 401-for-DeepSeek bug as resolveGeneralLLMConfig. Reads provider-specific baseUrl from config. * fix(deepseek): migrate retired persisted defaults --------- Co-authored-by: Joseph Magly <1159087+jmagly@users.noreply.github.com> --- docs/index.html | 10 +-- src/__tests__/deepseek-provider.test.ts | 35 +++++++-- .../provider-base-url-routing-static.test.ts | 19 +++++ src/config/index.ts | 75 +++++++++++++------ src/server.ts | 5 ++ src/setup.ts | 6 +- 6 files changed, 114 insertions(+), 36 deletions(-) create mode 100644 src/__tests__/provider-base-url-routing-static.test.ts diff --git a/docs/index.html b/docs/index.html index 83cc41e..7322aed 100755 --- a/docs/index.html +++ b/docs/index.html @@ -7037,9 +7037,9 @@ Correlating findings across agents, identifying patterns, producing actionable i { id: 'z-ai/glm-4.7', name: 'GLM 4.7', provider: 'Z.AI' }, // Meta { id: 'meta-llama/llama-3.3-70b', name: 'Llama 3.3 70B', provider: 'Meta' }, - // DeepSeek (Dec 2025) - { id: 'deepseek/deepseek-r1', name: 'DeepSeek R1', provider: 'DeepSeek' }, - { id: 'deepseek/deepseek-chat', name: 'DeepSeek V3', provider: 'DeepSeek' }, + // DeepSeek (Jul 2026 — V4) + { id: 'deepseek/deepseek-v4-pro', name: 'DeepSeek V4 Pro', provider: 'DeepSeek' }, + { id: 'deepseek/deepseek-v4-flash', name: 'DeepSeek V4 Flash', provider: 'DeepSeek' }, // Mistral { id: 'mistralai/mistral-large', name: 'Mistral Large', provider: 'Mistral' }, // Local @@ -25836,8 +25836,8 @@ Be specific and actionable. Format as structured JSON.`; const bb = _admBackbone(); try { // Op Admiral planning is a slow LLM call — cap it so a first-time user never gets a - // permanent "…drafting…" spinner if the model stalls (120s = generous but finite). - const r = await fetch('/api/admiral/launch', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(Object.assign({ brief: _admBrief('dry_run'), confirmed: false }, bb)), signal: AbortSignal.timeout(120000) }); + // permanent "…drafting…" spinner if the model stalls (180s). + const r = await fetch('/api/admiral/launch', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(Object.assign({ brief: _admBrief('dry_run'), confirmed: false }, bb)), signal: AbortSignal.timeout(180000) }); const d = await r.json(); if (d.error) throw new Error(d.error); const plan = d.plan || d.directive || d; diff --git a/src/__tests__/deepseek-provider.test.ts b/src/__tests__/deepseek-provider.test.ts index 6211a25..85d55f8 100644 --- a/src/__tests__/deepseek-provider.test.ts +++ b/src/__tests__/deepseek-provider.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it, afterEach } from 'vitest'; -import { config, AVAILABLE_MODELS } from '../config/index.js'; +import { + config, + AVAILABLE_MODELS, + migrateLegacyDeepSeekSettings, +} from '../config/index.js'; import { createDeepSeekBackbone } from '../llm/index.js'; const KEY = 'deepseek-key-abcdef123456'; @@ -7,12 +11,12 @@ const KEY = 'deepseek-key-abcdef123456'; describe('DeepSeek provider wiring (#62)', () => { afterEach(() => { delete process.env.DEEPSEEK_API_KEY; }); - it('resolves DeepSeek base URL, default model, and key from DEEPSEEK_API_KEY', () => { + it('resolves DeepSeek base URL, supported migrated model, and key from DEEPSEEK_API_KEY', () => { process.env.DEEPSEEK_API_KEY = KEY; const cfg = config.getLLMConfig('deepseek'); expect(cfg.provider).toBe('deepseek'); - expect(cfg.baseUrl).toBe('https://api.deepseek.com/v1'); - expect(cfg.model).toBe('deepseek-chat'); + expect(cfg.baseUrl).toBe('https://api.deepseek.com'); + expect(['deepseek-v4-flash', 'deepseek-v4-pro']).toContain(cfg.model); expect(cfg.apiKey).toBe(KEY); }); @@ -26,8 +30,29 @@ describe('DeepSeek provider wiring (#62)', () => { it('surfaces native DeepSeek models and configured provider state', () => { process.env.DEEPSEEK_API_KEY = KEY; expect(AVAILABLE_MODELS.deepseek?.map(m => m.id)).toEqual( - expect.arrayContaining(['deepseek-chat', 'deepseek-reasoner']), + expect.arrayContaining(['deepseek-v4-flash', 'deepseek-v4-pro']), ); expect(config.getConfiguredProviders()).toContain('deepseek'); }); + + it('migrates only retired DeepSeek defaults and preserves custom settings', () => { + expect(migrateLegacyDeepSeekSettings({ + baseUrl: 'https://api.deepseek.com/v1', + defaultModel: 'deepseek-chat', + })).toEqual({ + baseUrl: 'https://api.deepseek.com', + defaultModel: 'deepseek-v4-flash', + }); + expect(migrateLegacyDeepSeekSettings({ + baseUrl: 'https://api.deepseek.com/v1', + defaultModel: 'deepseek-reasoner', + }).defaultModel).toBe('deepseek-v4-pro'); + expect(migrateLegacyDeepSeekSettings({ + baseUrl: 'https://gateway.example/v1', + defaultModel: 'custom/deepseek', + })).toEqual({ + baseUrl: 'https://gateway.example/v1', + defaultModel: 'custom/deepseek', + }); + }); }); diff --git a/src/__tests__/provider-base-url-routing-static.test.ts b/src/__tests__/provider-base-url-routing-static.test.ts new file mode 100644 index 0000000..a7ab2b7 --- /dev/null +++ b/src/__tests__/provider-base-url-routing-static.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +describe('provider base URL routing regression', () => { + const serverSource = readFileSync(join(process.cwd(), 'src/server.ts'), 'utf8'); + + it('propagates the selected provider base URL into general LLM configuration', () => { + expect(serverSource).toMatch( + /function resolveGeneralLLMConfig[\s\S]*?baseUrl:\s*baseConfig\.baseUrl,/, + ); + }); + + it('propagates the selected provider base URL into operator execution', () => { + expect(serverSource).toMatch( + /function createTempestCommandInstance[\s\S]*?const baseConfig = config\.getLLMConfig\([\s\S]*?baseUrl:\s*baseConfig\.baseUrl,/, + ); + }); +}); diff --git a/src/config/index.ts b/src/config/index.ts index a05f5c3..ce090d1 100755 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -162,8 +162,8 @@ const DEFAULT_SETTINGS: TempestSettings = { }, deepseek: { - baseUrl: 'https://api.deepseek.com/v1', - defaultModel: 'deepseek-chat', + baseUrl: 'https://api.deepseek.com', + defaultModel: 'deepseek-v4-pro', }, codex: { @@ -188,6 +188,26 @@ const DEFAULT_SETTINGS: TempestSettings = { }, }; +export function migrateLegacyDeepSeekSettings( + settings: TempestSettings['deepseek'], +): TempestSettings['deepseek'] { + const migrated = { ...settings }; + + // DeepSeek retires these compatibility aliases on 2026-07-24. Only migrate + // the exact historical defaults; operator-selected model IDs and endpoints + // remain untouched. + if (migrated.defaultModel === 'deepseek-chat') { + migrated.defaultModel = 'deepseek-v4-flash'; + } else if (migrated.defaultModel === 'deepseek-reasoner') { + migrated.defaultModel = 'deepseek-v4-pro'; + } + if (migrated.baseUrl === 'https://api.deepseek.com/v1') { + migrated.baseUrl = 'https://api.deepseek.com'; + } + + return migrated; +} + // ============================================================================= // MODEL REGISTRY // ============================================================================= @@ -344,22 +364,22 @@ export const AVAILABLE_MODELS: Record = { maxOutput: 4096, capabilities: ['reasoning', 'code', 'analysis'], }, - // DeepSeek (Dec 2025) + // DeepSeek (Jul 2026 — V4) { - id: 'deepseek/deepseek-r1', - name: 'DeepSeek R1', + id: 'deepseek/deepseek-v4-pro', + name: 'DeepSeek V4 Pro', provider: 'DeepSeek', - contextWindow: 64000, - maxOutput: 8192, - capabilities: ['reasoning', 'code', 'analysis', 'complex-tasks'], + contextWindow: 1000000, + maxOutput: 384000, + capabilities: ['reasoning', 'code', 'analysis', 'complex-tasks', 'agents', 'tools'], }, { - id: 'deepseek/deepseek-chat', - name: 'DeepSeek V3', + id: 'deepseek/deepseek-v4-flash', + name: 'DeepSeek V4 Flash', provider: 'DeepSeek', - contextWindow: 64000, - maxOutput: 8192, - capabilities: ['reasoning', 'code', 'analysis'], + contextWindow: 1000000, + maxOutput: 384000, + capabilities: ['reasoning', 'code', 'analysis', 'tools'], }, // Mistral { @@ -504,20 +524,20 @@ export const AVAILABLE_MODELS: Record = { ], deepseek: [ { - id: 'deepseek-chat', - name: 'DeepSeek V3 (native)', + id: 'deepseek-v4-pro', + name: 'DeepSeek V4 Pro (native)', provider: 'DeepSeek', - contextWindow: 64000, - maxOutput: 8192, - capabilities: ['reasoning', 'code', 'analysis', 'tools'], + contextWindow: 1000000, + maxOutput: 384000, + capabilities: ['reasoning', 'code', 'analysis', 'complex-tasks', 'agents', 'tools'], }, { - id: 'deepseek-reasoner', - name: 'DeepSeek R1 (native)', + id: 'deepseek-v4-flash', + name: 'DeepSeek V4 Flash (native)', provider: 'DeepSeek', - contextWindow: 64000, - maxOutput: 8192, - capabilities: ['reasoning', 'code', 'analysis', 'complex-tasks'], + contextWindow: 1000000, + maxOutput: 384000, + capabilities: ['reasoning', 'code', 'analysis', 'tools'], }, ], local: [ @@ -574,6 +594,15 @@ class ConfigManager { defaults: DEFAULT_SETTINGS, }); + const deepseek = this.config.get('deepseek'); + const migratedDeepSeek = migrateLegacyDeepSeekSettings(deepseek); + if ( + migratedDeepSeek.baseUrl !== deepseek.baseUrl || + migratedDeepSeek.defaultModel !== deepseek.defaultModel + ) { + this.config.set('deepseek', migratedDeepSeek); + } + this.loadEnvVariables(); } diff --git a/src/server.ts b/src/server.ts index 56ae86c..838b273 100755 --- a/src/server.ts +++ b/src/server.ts @@ -323,12 +323,15 @@ function createTempestCommandInstance(missionName: string, apiKey: string | unde tempestCommand.stop(); } + const baseConfig = config.getLLMConfig(provider as any, model); + tempestCommand = new TempestCommand({ name: missionName, llm: { provider: provider as any, model, apiKey, + baseUrl: baseConfig.baseUrl, maxTokens: 4096, temperature: 0.7, }, @@ -6741,6 +6744,7 @@ function resolveGeneralLLMConfig(provider: string | undefined, model: string | u provider: any; model: string; apiKey?: string; + baseUrl?: string; maxTokens: number; temperature: number; timeout: number; @@ -6769,6 +6773,7 @@ function resolveGeneralLLMConfig(provider: string | undefined, model: string | u provider: selectedProvider as any, model: model || baseConfig.model, apiKey: effectiveKey, + baseUrl: baseConfig.baseUrl, maxTokens: 8192, temperature: 0.4, timeout: readGeneralTimeoutEnv() ?? 300000, // General planning needs room (was a hardcoded 60s); override via env diff --git a/src/setup.ts b/src/setup.ts index e80c18d..4463b8f 100755 --- a/src/setup.ts +++ b/src/setup.ts @@ -196,7 +196,7 @@ async function setupAnthropicKey(): Promise { async function setupDeepSeekKey(): Promise { console.log(''); - showInfo('DeepSeek provides OpenAI-compatible chat and reasoning models.'); + showInfo('DeepSeek provides OpenAI-compatible chat and reasoning models (V4 Flash / V4 Pro).'); showInfo('Get your API key at: ' + chalk.underline('https://platform.deepseek.com/api_keys')); console.log(''); @@ -220,8 +220,8 @@ async function setupDeepSeekKey(): Promise { try { const llm = new LLMBackbone({ provider: 'deepseek', - model: 'deepseek-chat', - baseUrl: 'https://api.deepseek.com/v1', + model: 'deepseek-v4-pro', + baseUrl: 'https://api.deepseek.com', apiKey, maxTokens: 10, temperature: 0,