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
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.

14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@
"minimum": 0,
"description": "%deepseek-copilot.config.maxTokens.description%"
},
"deepseek-copilot.contextSize": {
"type": "number",
"default": 1000000,
"enum": [200000, 1000000],
"enumItemLabels": [
"%deepseek-copilot.config.contextSize.200k.label%",
"%deepseek-copilot.config.contextSize.1m.label%"
],
"markdownEnumDescriptions": [
"%deepseek-copilot.config.contextSize.200k.description%",
"%deepseek-copilot.config.contextSize.1m.description%"
],
"markdownDescription": "%deepseek-copilot.config.contextSize.description%"
},
"deepseek-copilot.experimental.stabilizeToolList": {
"type": "boolean",
"default": false,
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
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
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
101 changes: 78 additions & 23 deletions src/provider/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,28 @@ 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 isBYOK: true;
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 @@ -46,16 +49,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,
maxOutputTokens: m.maxOutputTokens,
...resolveContextWindow(m, contextSize),
isBYOK: true,
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 @@ -74,24 +76,77 @@ 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;
/**
* Token split for the selectable 200K context window.
*
* VS Code/Copilot derives the displayed context window from
* `maxInputTokens + maxOutputTokens`, so each selectable window must split its
* *total* budget into input + output. The default 1M window keeps the
* accounting fixed in #71 (655,360 + 393,216 = 1,048,576 = DeepSeek's official
* combined input+output limit). The 200K option mirrors that same 5:3
* input:output reservation, scaled to a 200,000-token total, so the reported
* window stays honest (~200K) instead of input + a separate output reservation.
*/
const CONTEXT_WINDOW_200K = { maxInputTokens: 125000, maxOutputTokens: 75000 } as const;

/**
* Resolve the (input, output) token split for the selected context window.
* Unknown / unset values fall back to the model's own metadata, which encodes
* DeepSeek's official 1M (input + output) window.
*/
function resolveContextWindow(
m: ModelDefinition,
contextSize?: number,
): { maxInputTokens: number; maxOutputTokens: number } {
if (contextSize === 200000) {
return { ...CONTEXT_WINDOW_200K };
}
return { maxInputTokens: m.maxInputTokens, maxOutputTokens: m.maxOutputTokens };
}

/**
* 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
14 changes: 9 additions & 5 deletions src/provider/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import { t } from '../i18n';
import type { DeepSeekRequest } from '../types';
import { convertMessages, countMessageChars } from './convert';
import {
dumpDeepSeekRequest,
type CacheDiagnosticsRecorder,
type CacheDiagnosticsRun,
dumpDeepSeekRequest,
type CacheDiagnosticsRecorder,
type CacheDiagnosticsRun,
} from './debug';
import { getConfiguredThinkingEffort, type ModelConfigurationOptions } from './models';
import { classifyDeepSeekRequest, shouldForceThinkingNone, type RequestKind } from './routing';
import { getConfiguredContextSize, getConfiguredThinkingEffort, type ContextSize, type ModelConfigurationOptions } from './models';
import type { ReplayMarkerMetadata } from './replay';
import { classifyDeepSeekRequest, shouldForceThinkingNone, type RequestKind } from './routing';
import type { ConversationSegment } from './segment';
import { collectTrailingToolResultIds, prepareRequestTools } from './tools/request';
import { resolveImageMessages, type VisionDescriber } from './vision';
Expand All @@ -31,6 +31,8 @@ export interface PreparedChatRequest {
replayMarkerMetadata: ReplayMarkerMetadata;
visionMarkerTextChars?: number;
initialResponseNotice?: string;
/** The context size selected via the model-picker dropdown (if any). */
configuredContextSize: ContextSize;
}

export interface PrepareChatRequestOptions {
Expand Down Expand Up @@ -88,6 +90,7 @@ export async function prepareChatRequest({
const configuredThinkingEffort = getConfiguredThinkingEffort(
options as ModelConfigurationOptions,
);
const configuredContextSize = getConfiguredContextSize(options as ModelConfigurationOptions);
// Only force helper requests into disabled thinking on the official API.
// Custom endpoints keep their configured effort to preserve pre-#137 request shape.
const forceNoneThinking =
Expand Down Expand Up @@ -147,5 +150,6 @@ export async function prepareChatRequest({
replayMarkerMetadata: visionResolution.replayMarkerMetadata,
visionMarkerTextChars: visionResolution.stats.markerVisionTextChars || undefined,
initialResponseNotice: visionResolution.initialResponseNotice,
configuredContextSize,
};
}