Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,23 @@
"%deepseek-copilot.config.debugMode.verbose.description%"
],
"markdownDescription": "%deepseek-copilot.config.debugMode.description%"
},
"deepseek-copilot.tariff.transitionAlerts": {
"type": "boolean",
"default": true,
"markdownDescription": "Show a visible warning when a DeepSeek tariff transition is within the configured threshold."
},
"deepseek-copilot.tariff.transitionAudio": {
"type": "boolean",
"default": true,
"markdownDescription": "Play a system beep when a DeepSeek tariff transition warning triggers. Disable this if you do not want audio alerts."
},
"deepseek-copilot.tariff.transitionWarningMinutes": {
"type": "number",
"default": 5,
"minimum": 1,
"maximum": 60,
"markdownDescription": "How many minutes before a tariff transition to raise the warning."
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"deepseek-copilot.config.debugMode.metadata.description": "Privacy-safe metadata. Safe to share publicly. View with `DeepSeek: Show Logs`.",
"deepseek-copilot.config.debugMode.verbose.label": "Verbose",
"deepseek-copilot.config.debugMode.verbose.description": "⚠️ Contains sensitive prompt content. For local debugging only.",
"deepseek-copilot.config.tariff.transitionAlerts.description": "Show a visible warning when a DeepSeek tariff transition is within the configured threshold.",
"deepseek-copilot.config.tariff.transitionAudio.description": "Play a system beep when a DeepSeek tariff transition warning triggers.",
"deepseek-copilot.config.tariff.transitionWarningMinutes.description": "How many minutes before a tariff transition to raise the warning.",
"deepseek-copilot.config.modelIdOverrides.description": "Override the API model ID sent for each DeepSeek model. Defaults are prefilled with official DeepSeek IDs; change them only when using a compatible third-party API that uses different model names.",
"deepseek-copilot.config.modelIdOverrides.deepseek-v4-flash.description": "API model ID for DeepSeek V4 Flash",
"deepseek-copilot.config.modelIdOverrides.deepseek-v4-pro.description": "API model ID for DeepSeek V4 Pro",
Expand Down
148 changes: 148 additions & 0 deletions src/runtime/lifecycle.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,168 @@
import { spawnSync } from 'node:child_process';
import vscode from 'vscode';
import { t } from '../i18n';
import { logger } from '../logger';
import { DeepSeekChatProvider } from '../provider';
import {
getDeepSeekTariffState,
getDeepSeekTariffStatusText,
getNextDeepSeekTariffTransition,
refreshDeepSeekTariffWindowsFromPricingPage,
} from '../tariff';
import { registerActionUrls } from './actions';
import { registerCommands } from './commands';
import { initializeDiagnostics } from './diagnostics';
import { registerProvider } from './provider';
import { showWelcomeIfNeeded } from './welcome';

let activeProvider: DeepSeekChatProvider | undefined;
let lastTransitionWarningKey: string | undefined;
let lastTransitionFiredKey: string | undefined;

function playTariffWarningAudio(): void {
if (process.platform === 'win32') {
spawnSync('powershell', [
'-NoProfile',
'-Command',
'[Console]::Beep(880, 180)',
], {
stdio: 'ignore',
windowsHide: true,
});
return;
}
process.stdout.write('\u0007');
}

function playTariffTransitionAudio(): void {
if (process.platform === 'win32') {
spawnSync('powershell', [
'-NoProfile',
'-Command',
'[Console]::Beep(880, 220); Start-Sleep -Milliseconds 80; [Console]::Beep(1040, 260)',
], {
stdio: 'ignore',
windowsHide: true,
});
return;
}
process.stdout.write('\u0007\u0007');
}

export async function activate(context: vscode.ExtensionContext): Promise<void> {
await initializeDiagnostics(context);
registerCommands(context);
registerActionUrls(context);

const tariffStatusItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
100,
);
tariffStatusItem.name = 'DeepSeek Tariff';
tariffStatusItem.command = 'deepseek-copilot.openSettings';
context.subscriptions.push(tariffStatusItem);

const updateTariffStatus = () => {
const now = new Date();
const state = getDeepSeekTariffState(now);
const config = vscode.workspace.getConfiguration('deepseek-copilot');
const warningThresholdMinutes = config.get<number>('tariff.transitionWarningMinutes', 5);
const warningThresholdMs = Math.max(1, warningThresholdMinutes) * 60 * 1000;
const nextTransition = getNextDeepSeekTariffTransition(now);
const warningWindowActive =
nextTransition !== undefined &&
nextTransition.remainingMs <= warningThresholdMs &&
nextTransition.remainingMs > 0;
const transitionHappenedNow =
nextTransition !== undefined && nextTransition.remainingMs <= 1000 && nextTransition.remainingMs >= 0;
const warningPrefix = warningWindowActive ? '$(alert) ' : '';
const statusLabel = state === 'peak' ? '$(flame)' : '$(pulse)';
tariffStatusItem.text = `${warningPrefix}${statusLabel} DeepSeek: ${getDeepSeekTariffStatusText()}`;
tariffStatusItem.backgroundColor = warningWindowActive
? new vscode.ThemeColor('statusBarItem.warningBackground')
: transitionHappenedNow
? new vscode.ThemeColor('statusBarItem.prominentBackground')
: undefined;
tariffStatusItem.color = warningWindowActive
? new vscode.ThemeColor('statusBarItem.warningForeground')
: transitionHappenedNow
? new vscode.ThemeColor('statusBarItem.prominentForeground')
: undefined;
tariffStatusItem.tooltip = `DeepSeek model tariff: ${state === 'peak' ? 'Peak pricing is active (2x)' : 'Off-peak pricing is active (1/2 price)'} for the selected DeepSeek model.`;
tariffStatusItem.show();

if (!warningWindowActive && !transitionHappenedNow) {
lastTransitionWarningKey = undefined;
lastTransitionFiredKey = undefined;
return;
}

const key = `${nextTransition.at.toISOString()}-${nextTransition.from}->${nextTransition.to}`;
if (warningWindowActive) {
if (lastTransitionWarningKey === key) {
return;
}
lastTransitionWarningKey = key;
const alertsEnabled = config.get<boolean>('tariff.transitionAlerts', true);
if (!alertsEnabled) {
return;
}
const audioEnabled = config.get<boolean>('tariff.transitionAudio', true);
const direction = nextTransition.to === 'peak' ? 'on-peak' : 'off-peak';
const remainingMinutes = Math.max(1, Math.ceil(nextTransition.remainingMs / 60000));
void vscode.window.showWarningMessage(
`DeepSeek tariff change in ${remainingMinutes} minute${remainingMinutes === 1 ? '' : 's'}: ${direction} begins at ${nextTransition.at.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', timeZone: 'UTC', hour12: false })} UTC.`,
);
if (audioEnabled) {
playTariffWarningAudio();
}
return;
}

if (transitionHappenedNow && lastTransitionFiredKey !== key) {
lastTransitionFiredKey = key;
const alertsEnabled = config.get<boolean>('tariff.transitionAlerts', true);
const audioEnabled = config.get<boolean>('tariff.transitionAudio', true);
const direction = nextTransition.to === 'peak' ? 'on-peak' : 'off-peak';
if (alertsEnabled) {
void vscode.window.showInformationMessage(
`DeepSeek tariff transition: ${direction} has started. Current mode is ${nextTransition.to.toUpperCase()}.`,
);
}
if (audioEnabled) {
playTariffTransitionAudio();
}
}
};

updateTariffStatus();
const timer = setInterval(updateTariffStatus, 1000);
context.subscriptions.push({ dispose: () => clearInterval(timer) });

const refreshTariffSchedule = () =>
void refreshDeepSeekTariffWindowsFromPricingPage(
'https://api-docs.deepseek.com/quick_start/pricing',
context.globalState,
).then((windows) => {
const previous = context.globalState.get<{ windows: typeof windows; footnote?: string }>(
'deepseek-copilot.tariff.schedule',
);
if (previous && previous.windows && previous.windows.length > 0) {
const changed = !previous.windows.every((window, index) => {
const next = windows[index];
return next && window.startHourUtc === next.startHourUtc && window.endHourUtc === next.endHourUtc;
});
if (changed) {
logger.info(`DeepSeek tariff schedule changed, refreshed windows=${JSON.stringify(windows)}`);
}
}
}).catch((error) => {
logger.warn('Failed to refresh DeepSeek tariff schedule from pricing page', error);
});
refreshTariffSchedule();
const tariffRefreshTimer = setInterval(refreshTariffSchedule, 60 * 60 * 1000);
context.subscriptions.push({ dispose: () => clearInterval(tariffRefreshTimer) });

try {
const provider = await registerProvider(context);
activeProvider = provider;
Expand Down
64 changes: 64 additions & 0 deletions src/tariff.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import {
getDeepSeekTariffState,
getDeepSeekTariffWindowsFromPricingFootnote,
getNextDeepSeekTariffTransition,
hasDeepSeekTariffScheduleChanged,
} from './tariff';

describe('DeepSeek tariff logic', () => {
it('marks weekdays peak windows as 2x pricing', () => {
const peak = new Date('2026-08-24T02:30:00Z');
assert.equal(getDeepSeekTariffState(peak), 'peak');
});

it('marks non-peak times as half price', () => {
const offPeak = new Date('2026-08-24T05:00:00Z');
assert.equal(getDeepSeekTariffState(offPeak), 'offpeak');
});

it('finds the next transition for a state change', () => {
const now = new Date('2026-08-24T03:30:00Z');
const next = getNextDeepSeekTariffTransition(now);
assert.ok(next);
assert.equal(next.to, 'offpeak');
assert.equal(next.at.getUTCHours(), 4);
});

it('parses the pricing page footnote window schedule', () => {
const windows = getDeepSeekTariffWindowsFromPricingFootnote(
'(1) Off-peak rates are half of the peak rates. Peak hours are 01:00 - 04:00 and 06:00 - 10:00 UTC, Monday through Friday (all other hours are off-peak).',
);
assert.deepEqual(windows, [
{ startHourUtc: 1, endHourUtc: 4 },
{ startHourUtc: 6, endHourUtc: 10 },
]);
});

it('parses the pricing page when the note appears elsewhere on the page', () => {
const windows = getDeepSeekTariffWindowsFromPricingFootnote(
'Pricing details box\nImportant note: Peak hours are 01:00 - 04:00 and 06:00 - 10:00 UTC, Monday through Friday (all other hours are off-peak).\nMore pricing tables below.',
);
assert.deepEqual(windows, [
{ startHourUtc: 1, endHourUtc: 4 },
{ startHourUtc: 6, endHourUtc: 10 },
]);
});

it('detects when the pricing website schedule changes from the footnote', () => {
assert.equal(
hasDeepSeekTariffScheduleChanged(
'(1) Off-peak rates are half of the peak rates. Peak hours are 01:00 - 04:00 and 06:00 - 10:00 UTC, Monday through Friday (all other hours are off-peak).',
),
false,
);
assert.equal(
hasDeepSeekTariffScheduleChanged(
'(1) Off-peak rates are half of the peak rates. Peak hours are 00:00 - 03:00 and 05:00 - 09:00 UTC, Monday through Friday (all other hours are off-peak).',
),
true,
);
});
});
Loading