Skip to content
Closed
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)}`;
}
8 changes: 5 additions & 3 deletions src/client/core.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { CancellationToken } from 'vscode';
import { safeStringify } from '../json';
import { logger } from '../logger';
import type { ChatCompletionRequestBody } from '../provider/thinking';
import type {
DeepSeekRequest,
DeepSeekStreamChunk,
DeepSeekToolCall,
DeepSeekUsage,
Expand All @@ -25,7 +25,7 @@ export class DeepSeekClient {
* Parses SSE chunks and dispatches callbacks for content, thinking, and tool calls.
*/
async streamChatCompletion(
request: DeepSeekRequest,
request: ChatCompletionRequestBody,
callbacks: StreamCallbacks,
cancellationToken?: CancellationToken,
): Promise<void> {
Expand Down Expand Up @@ -81,7 +81,9 @@ export class DeepSeekClient {
break;
}

buffer += decoder.decode(value, { stream: true });
const decoded = decoder.decode(value, { stream: true });
callbacks.onRawResponseData?.(decoded);
buffer += decoded;

const lines = buffer.split('\n');
buffer = lines.pop() || '';
Expand Down
12 changes: 12 additions & 0 deletions src/client/error/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ export class DeepSeekRequestError extends Error {
readonly diagnosticMessage: string;
readonly baseUrl?: string;
readonly status?: number;
readonly statusText?: string;
readonly code?: string;
readonly serverMessage?: string;
readonly responseText?: string;

constructor(options: {
message: string;
Expand All @@ -44,7 +47,10 @@ export class DeepSeekRequestError extends Error {
diagnosticMessage?: string;
baseUrl?: string;
status?: number;
statusText?: string;
code?: string;
serverMessage?: string;
responseText?: string;
cause?: unknown;
}) {
super(options.message, { cause: options.cause });
Expand All @@ -54,7 +60,10 @@ export class DeepSeekRequestError extends Error {
this.diagnosticMessage = options.diagnosticMessage ?? options.message;
this.baseUrl = options.baseUrl;
this.status = options.status;
this.statusText = options.statusText;
this.code = options.code;
this.serverMessage = options.serverMessage;
this.responseText = options.responseText;
}
}

Expand All @@ -76,7 +85,10 @@ export async function createHttpError(
kind: 'http',
baseUrl,
status: response.status,
statusText: response.statusText,
code: `HTTP_${response.status}`,
serverMessage,
responseText,
diagnosticMessage: joinDiagnosticParts(
`kind=http`,
`status=${response.status}`,
Expand Down
4 changes: 2 additions & 2 deletions src/client/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DeepSeekRequest } from '../types';
import type { ChatCompletionRequestBody } from '../provider/thinking';

export interface ErrorActionUrls {
configureApiKey?: string;
Expand All @@ -7,7 +7,7 @@ export interface ErrorActionUrls {

export interface RequestErrorContext {
baseUrl: string;
request: DeepSeekRequest;
request: ChatCompletionRequestBody;
}

export interface ErrorActionLink {
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
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
Loading