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,