Skip to content
Closed
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
12 changes: 0 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,23 @@
"title": "%deepseek-copilot.command.openRequestDumpsFolder%"
}
],
"views": {
"deepseek-copilot": [
{
"id": "deepseek-copilot.panelView",
"name": "DeepSeek",
"type": "webview"
}
]
},
"viewsContainers": {
"panel": [
{
"id": "deepseek-copilot",
"title": "DeepSeek"
}
]
},
"walkthroughs": [
{
"id": "deepseekGettingStarted",
Expand Down Expand Up @@ -168,6 +185,12 @@
"default": "Describe all image attachments in this message.\n\nIf there is one image, describe it directly.\nIf there are multiple images:\n1. Describe each image separately, preserving their order.\n2. Then provide a combined description explaining the overall context and relationships across the images.\n\nReturn one concise factual description suitable for inserting into a text-only chat prompt. Include visible text, objects, UI elements, people, and relevant context. Do not invent details.",
"markdownDescription": "%deepseek-copilot.config.visionPrompt.description%"
},
"deepseek-copilot.contextSize": {
"type": "number",
"default": 1000000,
"enum": [200000, 1000000],
"description": "Context window size (tokens). 200K or 1M."
},
"deepseek-copilot.debugMode": {
"type": "string",
"default": "minimal",
Expand Down
5 changes: 5 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
"deepseek-copilot.config.title": "DeepSeek Copilot",
"deepseek-copilot.config.baseUrl.description": "DeepSeek API base URL. Defaults to official DeepSeek API endpoint.",
"deepseek-copilot.config.maxTokens.description": "Maximum number of output tokens per request. Set to 0 to use the API default (no limit). Useful for controlling costs.",
"deepseek-copilot.config.contextSize.description": "Input context window size reported to VS Code. Larger context allows longer conversations without compaction but may increase cost.",
"deepseek-copilot.config.contextSize.200k.label": "200K",
"deepseek-copilot.config.contextSize.200k.description": "200K tokens — safe default for most sessions.",
"deepseek-copilot.config.contextSize.1m.label": "1M",
"deepseek-copilot.config.contextSize.1m.description": "1M tokens — longer sessions without compaction.",
"deepseek-copilot.config.experimental.stabilizeToolList.description": "**Experimental**: improve DeepSeek context-cache hit rate by pre-activating available tools.\n- When the enabled tools list changes across turns, this may improve DeepSeek context-cache hit rate.\n- Requests will include more function definitions, so input tokens may increase. Cache-hit input tokens are billed at a lower price, but still count toward usage.\n- This may add internal preflight tool calls to the current Copilot chat history. If you switch to another model in the same conversation, that model provider may reject or mishandle the replayed history. Start a new chat if model switching behaves unexpectedly.\n\nUse [Configure Tools](command:workbench.action.chat.configureTools) to **view and manage** your tool list:\n\n- 64 or fewer enabled tools: usually no need to enable this unless the tool list still changes across turns.\n- More than 128 enabled tools: not recommended. DeepSeek supports at most 128 functions in one `tools` request. Consider disabling tools you rarely use.",
"deepseek-copilot.config.debugMode.description": "Controls what diagnostic information DeepSeek Copilot writes. Token usage is always reported to Copilot regardless of this setting.\n\n- **Minimal** — Token usage only. No diagnostic logs or request dumps.\n- **Metadata** — Privacy-safe diagnostic metadata (request hashes, prefix overlap, tool schema changes). Does not contain prompt text — safe to share in public issue reports. View with [`DeepSeek: Show Logs`](command:deepseek-copilot.showLogs).\n- **Verbose** — Complete request payloads written to disk for local debugging. **Warning: contains sensitive prompt content.** View with [`DeepSeek: Open Request Dumps Folder`](command:deepseek-copilot.openRequestDumpsFolder).",
"deepseek-copilot.config.debugMode.minimal.label": "Minimal",
Expand Down
51 changes: 51 additions & 0 deletions src/balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import vscode from 'vscode';
import { getBaseUrl } from './config';

export interface BalanceInfo {
/** Single balance entry (DeepSeek returns array of these). */
currency: string;
total_balance: string;
granted_balance: string;
topped_up_balance: string;
}

export interface BalanceResponse {
is_available: boolean;
balance_infos: BalanceInfo[];
}

/**
* Fetch DeepSeek user balance from the API.
* Returns the raw response or undefined on failure.
*/
export async function fetchBalance(apiKey: string): Promise<BalanceResponse | undefined> {
try {
const baseUrl = getBaseUrl();
const response = await fetch(`${baseUrl}/user/balance`, {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${apiKey}`,
},
});

if (!response.ok) {
void vscode.window.showWarningMessage(`DeepSeek balance check failed: HTTP ${response.status}`);
return undefined;
}

return (await response.json()) as BalanceResponse;
} catch (error) {
void vscode.window.showWarningMessage(`DeepSeek balance check failed: ${error instanceof Error ? error.message : String(error)}`);
return undefined;
}
}

/**
* Format a balance entry for display.
*/
export function formatBalance(balance: BalanceInfo): string {
const total = parseFloat(balance.total_balance);
const granted = parseFloat(balance.granted_balance);
const toppedUp = parseFloat(balance.topped_up_balance);
return `${balance.currency} ${total.toFixed(2)}`;
}
9 changes: 9 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ export function getMaxTokens(): number | undefined {
return value > 0 ? value : undefined;
}

/**
* Get the configured context window size in tokens.
* Returns the value set by the user (200K or 1M), defaulting to 1M.
*/
export function getContextSize(): number {
const config = vscode.workspace.getConfiguration(CONFIG_SECTION);
return config.get<number>('contextSize', 1000000);
}

/**
* Diagnostic mode. `verbose` also enables metadata logs.
*
Expand Down
8 changes: 4 additions & 4 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export const MODELS: ModelDefinition[] = [
family: 'deepseek',
version: 'v4',
detail: 'Fast, general-purpose model',
maxInputTokens: 655360,
maxOutputTokens: 393216,
maxInputTokens: 1000000,
maxOutputTokens: 128000,
capabilities: {
toolCalling: DEEPSEEK_TOOLS_LIMIT,
imageInput: true,
Expand All @@ -74,8 +74,8 @@ export const MODELS: ModelDefinition[] = [
family: 'deepseek',
version: 'v4',
detail: 'Most capable reasoning model',
maxInputTokens: 655360,
maxOutputTokens: 393216,
maxInputTokens: 1000000,
maxOutputTokens: 128000,
capabilities: {
toolCalling: DEEPSEEK_TOOLS_LIMIT,
imageInput: true,
Expand Down
14 changes: 14 additions & 0 deletions src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ const zh: Translations = {
'thinking.max': '深度',
'thinking.max.desc': '深度推理,适合复杂任务',

// Context Size — model picker dropdown
'contextSize.title': '上下文窗口',
'contextSize.200k': '200K',
'contextSize.200k.desc': '200K token 上下文(更快)',
'contextSize.1m': '1M',
'contextSize.1m.desc': '1M token 上下文(更大容量)',

// Vision
'vision.proxyUsing': '视觉代理:{0}',
'vision.notFound': '未找到视觉模型 "{0}"',
Expand Down Expand Up @@ -232,6 +239,13 @@ const en: Translations = {
'thinking.max': 'Max',
'thinking.max.desc': 'Maximum reasoning depth for complex agent tasks',

// Context Size — model picker dropdown
'contextSize.title': 'Context Window',
'contextSize.200k': '200K',
'contextSize.200k.desc': '200K token context (faster)',
'contextSize.1m': '1M',
'contextSize.1m.desc': '1M token context (larger capacity)',

// Vision
// NOTE: vision.unableToDescribe has been moved to consts.ts as
// IMAGE_DESCRIPTION_UNAVAILABLE — it is prompt content, not UI text.
Expand Down
18 changes: 15 additions & 3 deletions src/provider/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import vscode from 'vscode';
import { AuthManager } from '../auth';
import { getStabilizeToolListEnabled } from '../config';
import { MODELS } from '../consts';
import { getContextSize, getStabilizeToolListEnabled } from '../config';
import { CONFIG_SECTION, MODELS } from '../consts';
import { t } from '../i18n';
import { logger } from '../logger';
import { createCacheDiagnosticsRecorder, dumpProviderInput } from './debug';
Expand Down Expand Up @@ -133,10 +133,11 @@ export class DeepSeekChatProvider implements vscode.LanguageModelChatProvider {

const hasKey = await this.authManager.hasApiKey();
const pricingCurrency = this.balanceCurrencyResolver.getDisplayCurrency();
const contextSize = getContextSize();
if (hasKey) {
this.balanceCurrencyResolver.refreshInBackground();
}
return MODELS.map((model) => toChatInfo(model, hasKey, pricingCurrency));
return MODELS.map((model) => toChatInfo(model, hasKey, pricingCurrency, contextSize));
}

async provideLanguageModelChatResponse(
Expand Down Expand Up @@ -184,6 +185,17 @@ export class DeepSeekChatProvider implements vscode.LanguageModelChatProvider {
getVisionDescriber: () => this.vision.get(),
});

// Sync context size back to VS Code setting when user changes it via the
// model-picker dropdown. The updated maxInputTokens takes effect on the
// next request after Copilot Chat re-queries model information.
const currentContextSize = getContextSize();
if (prepared.configuredContextSize !== currentContextSize) {
await vscode.workspace
.getConfiguration(CONFIG_SECTION)
.update('contextSize', prepared.configuredContextSize, vscode.ConfigurationTarget.Global);
this.onDidChangeLanguageModelChatInformationEmitter.fire();
}

return streamChatCompletion({
prepared,
progress,
Expand Down
72 changes: 50 additions & 22 deletions src/provider/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,27 @@ import { toModelCostInfo, type ModelCostInformation } from './pricing/costs';

export type ThinkingEffort = 'none' | 'high' | 'max';

export type ContextSize = 200000 | 1000000;

export type ModelConfigurationOptions = vscode.ProvideLanguageModelChatResponseOptions & {
readonly modelConfiguration?: Record<string, unknown>;
readonly configuration?: Record<string, unknown>;
};

type ThinkingEffortConfigurationSchema = ReturnType<typeof buildThinkingEffortSchema>;
type ModelConfigurationSchema = ReturnType<typeof buildModelConfigurationSchema>;

export type ModelPickerChatInformation = vscode.LanguageModelChatInformation &
ModelCostInformation & {
readonly isUserSelectable: boolean;
readonly statusIcon?: vscode.ThemeIcon;
readonly configurationSchema?: ThinkingEffortConfigurationSchema;
readonly configurationSchema?: ModelConfigurationSchema;
};

export function toChatInfo(
m: ModelDefinition,
hasApiKey: boolean,
pricingCurrency?: PricingCurrency,
contextSize?: number,
): ModelPickerChatInformation {
const modelDetail = resolveModelText(m, 'detail') ?? m.detail;
const modelTooltip = resolveModelText(m, 'tooltip');
Expand All @@ -44,15 +47,15 @@ export function toChatInfo(
detail: hasApiKey ? modelDetail : t('auth.apiKeyRequiredDetail'),
tooltip: hasApiKey ? modelTooltip : t('auth.apiKeyRequiredDetail'),
statusIcon: hasApiKey ? undefined : new vscode.ThemeIcon('warning'),
maxInputTokens: m.maxInputTokens,
maxInputTokens: contextSize ?? m.maxInputTokens,
maxOutputTokens: m.maxOutputTokens,
isUserSelectable: true,
capabilities: {
toolCalling: m.capabilities.toolCalling,
imageInput: m.capabilities.imageInput,
},
...toModelCostInfo(m, pricingCurrency),
...(m.capabilities.thinking ? { configurationSchema: buildThinkingEffortSchema() } : {}),
...(hasApiKey ? { configurationSchema: buildModelConfigurationSchema(m) } : {}),
};
}

Expand All @@ -71,24 +74,49 @@ export function getConfiguredThinkingEffort(options: ModelConfigurationOptions):
return configuredEffort === 'max' ? 'max' : 'high';
}

function buildThinkingEffortSchema() {
return {
properties: {
reasoningEffort: {
type: 'string',
title: t('status.thinking'),
enum: ['none', 'high', 'max'],
enumItemLabels: [t('thinking.none'), t('thinking.high'), t('thinking.max')],
enumDescriptions: [
t('thinking.none.desc'),
t('thinking.high.desc'),
t('thinking.max.desc'),
],
default: 'high',
group: 'navigation',
},
},
} as const;
/**
* Read the context size selected by the user via the model-picker dropdown.
* Falls back to the VS Code setting when the dropdown hasn't been used yet.
*/
export function getConfiguredContextSize(options: ModelConfigurationOptions): ContextSize {
const configured =
options.modelConfiguration?.contextSize ?? options.configuration?.contextSize;
if (configured === 200000) {
return 200000;
}
return 1000000;
}

function buildModelConfigurationSchema(m: ModelDefinition) {
const properties: Record<string, unknown> = {};

if (m.capabilities.thinking) {
properties.reasoningEffort = {
type: 'string',
title: t('status.thinking'),
enum: ['none', 'high', 'max'],
enumItemLabels: [t('thinking.none'), t('thinking.high'), t('thinking.max')],
enumDescriptions: [
t('thinking.none.desc'),
t('thinking.high.desc'),
t('thinking.max.desc'),
],
default: 'high',
group: 'navigation',
};
}

properties.contextSize = {
type: 'number',
title: t('contextSize.title'),
enum: [200000, 1000000],
enumItemLabels: [t('contextSize.200k'), t('contextSize.1m')],
enumDescriptions: [t('contextSize.200k.desc'), t('contextSize.1m.desc')],
default: 1000000,
group: 'tokens',
};

return { properties } as const;
}

function resolveModelText(m: ModelDefinition, field: 'detail' | 'tooltip'): string | undefined {
Expand Down
Loading