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:
1 parent
9cce68e804
commit
440dedd9c1
6 files changed
+114
-36
No files matched your search
+5
-5
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
@@ -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<LLMProvider, ModelInfo[]> = {
|
||||
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<LLMProvider, ModelInfo[]> = {
|
||||
],
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-3
@@ -196,7 +196,7 @@ async function setupAnthropicKey(): Promise<boolean> {
|
||||
|
||||
async function setupDeepSeekKey(): Promise<boolean> {
|
||||
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<boolean> {
|
||||
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,
|
||||
|
||||
Reference in new issue
Block a user