feat: update DeepSeek models to V4 Flash and V4 Pro (#103)

* 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>
This commit is contained in:
Krzysztof MarciniakandJoseph Magly authored and GitHub committed 2026-07-20 11:36:50 -04:00
1 parent 9cce68e804
commit 440dedd9c1
6 files changed
+114 -36

No files matched your search

+5 -5
View File
@@ -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' }, { id: 'z-ai/glm-4.7', name: 'GLM 4.7', provider: 'Z.AI' },
// Meta // Meta
{ id: 'meta-llama/llama-3.3-70b', name: 'Llama 3.3 70B', provider: 'Meta' }, { id: 'meta-llama/llama-3.3-70b', name: 'Llama 3.3 70B', provider: 'Meta' },
// DeepSeek (Dec 2025) // DeepSeek (Jul 2026 — V4)
{ id: 'deepseek/deepseek-r1', name: 'DeepSeek R1', provider: 'DeepSeek' }, { id: 'deepseek/deepseek-v4-pro', name: 'DeepSeek V4 Pro', provider: 'DeepSeek' },
{ id: 'deepseek/deepseek-chat', name: 'DeepSeek V3', provider: 'DeepSeek' }, { id: 'deepseek/deepseek-v4-flash', name: 'DeepSeek V4 Flash', provider: 'DeepSeek' },
// Mistral // Mistral
{ id: 'mistralai/mistral-large', name: 'Mistral Large', provider: 'Mistral' }, { id: 'mistralai/mistral-large', name: 'Mistral Large', provider: 'Mistral' },
// Local // Local
@@ -25836,8 +25836,8 @@ Be specific and actionable. Format as structured JSON.`;
const bb = _admBackbone(); const bb = _admBackbone();
try { try {
// Op Admiral planning is a slow LLM call — cap it so a first-time user never gets a // 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). // 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(120000) }); 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(); const d = await r.json();
if (d.error) throw new Error(d.error); if (d.error) throw new Error(d.error);
const plan = d.plan || d.directive || d; const plan = d.plan || d.directive || d;
+30 -5
View File
@@ -1,5 +1,9 @@
import { describe, expect, it, afterEach } from 'vitest'; 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'; import { createDeepSeekBackbone } from '../llm/index.js';
const KEY = 'deepseek-key-abcdef123456'; const KEY = 'deepseek-key-abcdef123456';
@@ -7,12 +11,12 @@ const KEY = 'deepseek-key-abcdef123456';
describe('DeepSeek provider wiring (#62)', () => { describe('DeepSeek provider wiring (#62)', () => {
afterEach(() => { delete process.env.DEEPSEEK_API_KEY; }); 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; process.env.DEEPSEEK_API_KEY = KEY;
const cfg = config.getLLMConfig('deepseek'); const cfg = config.getLLMConfig('deepseek');
expect(cfg.provider).toBe('deepseek'); expect(cfg.provider).toBe('deepseek');
expect(cfg.baseUrl).toBe('https://api.deepseek.com/v1'); expect(cfg.baseUrl).toBe('https://api.deepseek.com');
expect(cfg.model).toBe('deepseek-chat'); expect(['deepseek-v4-flash', 'deepseek-v4-pro']).toContain(cfg.model);
expect(cfg.apiKey).toBe(KEY); expect(cfg.apiKey).toBe(KEY);
}); });
@@ -26,8 +30,29 @@ describe('DeepSeek provider wiring (#62)', () => {
it('surfaces native DeepSeek models and configured provider state', () => { it('surfaces native DeepSeek models and configured provider state', () => {
process.env.DEEPSEEK_API_KEY = KEY; process.env.DEEPSEEK_API_KEY = KEY;
expect(AVAILABLE_MODELS.deepseek?.map(m => m.id)).toEqual( 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'); 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',
});
});
}); });
@@ -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,/,
);
});
});
+52 -23
View File
@@ -162,8 +162,8 @@ const DEFAULT_SETTINGS: TempestSettings = {
}, },
deepseek: { deepseek: {
baseUrl: 'https://api.deepseek.com/v1', baseUrl: 'https://api.deepseek.com',
defaultModel: 'deepseek-chat', defaultModel: 'deepseek-v4-pro',
}, },
codex: { 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 // MODEL REGISTRY
// ============================================================================= // =============================================================================
@@ -344,22 +364,22 @@ export const AVAILABLE_MODELS: Record<LLMProvider, ModelInfo[]> = {
maxOutput: 4096, maxOutput: 4096,
capabilities: ['reasoning', 'code', 'analysis'], capabilities: ['reasoning', 'code', 'analysis'],
}, },
// DeepSeek (Dec 2025) // DeepSeek (Jul 2026 — V4)
{ {
id: 'deepseek/deepseek-r1', id: 'deepseek/deepseek-v4-pro',
name: 'DeepSeek R1', name: 'DeepSeek V4 Pro',
provider: 'DeepSeek', provider: 'DeepSeek',
contextWindow: 64000, contextWindow: 1000000,
maxOutput: 8192, maxOutput: 384000,
capabilities: ['reasoning', 'code', 'analysis', 'complex-tasks'], capabilities: ['reasoning', 'code', 'analysis', 'complex-tasks', 'agents', 'tools'],
}, },
{ {
id: 'deepseek/deepseek-chat', id: 'deepseek/deepseek-v4-flash',
name: 'DeepSeek V3', name: 'DeepSeek V4 Flash',
provider: 'DeepSeek', provider: 'DeepSeek',
contextWindow: 64000, contextWindow: 1000000,
maxOutput: 8192, maxOutput: 384000,
capabilities: ['reasoning', 'code', 'analysis'], capabilities: ['reasoning', 'code', 'analysis', 'tools'],
}, },
// Mistral // Mistral
{ {
@@ -504,20 +524,20 @@ export const AVAILABLE_MODELS: Record<LLMProvider, ModelInfo[]> = {
], ],
deepseek: [ deepseek: [
{ {
id: 'deepseek-chat', id: 'deepseek-v4-pro',
name: 'DeepSeek V3 (native)', name: 'DeepSeek V4 Pro (native)',
provider: 'DeepSeek', provider: 'DeepSeek',
contextWindow: 64000, contextWindow: 1000000,
maxOutput: 8192, maxOutput: 384000,
capabilities: ['reasoning', 'code', 'analysis', 'tools'], capabilities: ['reasoning', 'code', 'analysis', 'complex-tasks', 'agents', 'tools'],
}, },
{ {
id: 'deepseek-reasoner', id: 'deepseek-v4-flash',
name: 'DeepSeek R1 (native)', name: 'DeepSeek V4 Flash (native)',
provider: 'DeepSeek', provider: 'DeepSeek',
contextWindow: 64000, contextWindow: 1000000,
maxOutput: 8192, maxOutput: 384000,
capabilities: ['reasoning', 'code', 'analysis', 'complex-tasks'], capabilities: ['reasoning', 'code', 'analysis', 'tools'],
}, },
], ],
local: [ local: [
@@ -574,6 +594,15 @@ class ConfigManager {
defaults: DEFAULT_SETTINGS, 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(); this.loadEnvVariables();
} }
+5
View File
@@ -323,12 +323,15 @@ function createTempestCommandInstance(missionName: string, apiKey: string | unde
tempestCommand.stop(); tempestCommand.stop();
} }
const baseConfig = config.getLLMConfig(provider as any, model);
tempestCommand = new TempestCommand({ tempestCommand = new TempestCommand({
name: missionName, name: missionName,
llm: { llm: {
provider: provider as any, provider: provider as any,
model, model,
apiKey, apiKey,
baseUrl: baseConfig.baseUrl,
maxTokens: 4096, maxTokens: 4096,
temperature: 0.7, temperature: 0.7,
}, },
@@ -6741,6 +6744,7 @@ function resolveGeneralLLMConfig(provider: string | undefined, model: string | u
provider: any; provider: any;
model: string; model: string;
apiKey?: string; apiKey?: string;
baseUrl?: string;
maxTokens: number; maxTokens: number;
temperature: number; temperature: number;
timeout: number; timeout: number;
@@ -6769,6 +6773,7 @@ function resolveGeneralLLMConfig(provider: string | undefined, model: string | u
provider: selectedProvider as any, provider: selectedProvider as any,
model: model || baseConfig.model, model: model || baseConfig.model,
apiKey: effectiveKey, apiKey: effectiveKey,
baseUrl: baseConfig.baseUrl,
maxTokens: 8192, maxTokens: 8192,
temperature: 0.4, temperature: 0.4,
timeout: readGeneralTimeoutEnv() ?? 300000, // General planning needs room (was a hardcoded 60s); override via env timeout: readGeneralTimeoutEnv() ?? 300000, // General planning needs room (was a hardcoded 60s); override via env
+3 -3
View File
@@ -196,7 +196,7 @@ async function setupAnthropicKey(): Promise<boolean> {
async function setupDeepSeekKey(): Promise<boolean> { async function setupDeepSeekKey(): Promise<boolean> {
console.log(''); 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')); showInfo('Get your API key at: ' + chalk.underline('https://platform.deepseek.com/api_keys'));
console.log(''); console.log('');
@@ -220,8 +220,8 @@ async function setupDeepSeekKey(): Promise<boolean> {
try { try {
const llm = new LLMBackbone({ const llm = new LLMBackbone({
provider: 'deepseek', provider: 'deepseek',
model: 'deepseek-chat', model: 'deepseek-v4-pro',
baseUrl: 'https://api.deepseek.com/v1', baseUrl: 'https://api.deepseek.com',
apiKey, apiKey,
maxTokens: 10, maxTokens: 10,
temperature: 0, temperature: 0,