Skip to content

Commit c20565a

Browse files
authored
feat(provider): add segment-aware request diagnostics (#66)
- add provider-owned conversation segment markers via Copilot stateful_marker - group verbose request dumps by conversationSegmentId - replace legacy debug setting with debugMode levels - expand diagnostics for segment resolution and marker reporting - preserve request dump folder URI for remote extension hosts
1 parent a477c1e commit c20565a

16 files changed

Lines changed: 1257 additions & 47 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Both support optional thinking mode, tool calling, and 1M token context.
102102
| `deepseek-copilot.baseUrl` | `https://api.deepseek.com` | API endpoint — change for self-hosted / proxied deployments |
103103
| `deepseek-copilot.maxTokens` | `0` | Max output tokens (`0` = no limit). Useful for cost control |
104104
| `deepseek-copilot.modelIdOverrides` | prefilled official ID map | API model IDs to send for DeepSeek V4 Flash / Pro. Change only for compatible third-party APIs with different model names |
105-
| `deepseek-copilot.debug` | `false` | Enable privacy-preserving diagnostic debug logs for troubleshooting. Does not log prompt text |
105+
| `deepseek-copilot.debugMode` | `minimal` | Diagnostic mode: `minimal` for token usage only, `metadata` for privacy-preserving logs, or `verbose` for full request dumps and pipeline snapshots under extension global storage. Full dumps may include sensitive prompt text, tool schemas, file snippets, and image descriptions. Use `DeepSeek: Open Request Dumps Folder` to open the dump location |
106106
| `deepseek-copilot.visionModel` | *(auto)* | Which Copilot model to proxy images through |
107107
| `deepseek-copilot.visionPrompt` | *(built-in)* | Prompt used to describe image attachments |
108108

README.zh-cn.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ API Key 存储在 VS Code 的 `SecretStorage` 中(macOS 钥匙串 / Windows
102102
| `deepseek-copilot.baseUrl` | `https://api.deepseek.com` | API 端点——可改为自托管或代理部署地址 |
103103
| `deepseek-copilot.maxTokens` | `0` | 最大输出 Token 数(`0` = 不限制)。可用于成本控制 |
104104
| `deepseek-copilot.modelIdOverrides` | 预填官方 ID 映射 | DeepSeek V4 Flash / Pro 对应的 API 模型 ID。仅在使用模型名不同的兼容第三方 API 时修改 |
105-
| `deepseek-copilot.debug` | `false` | 启用隐私安全的诊断调试日志。不会记录提示词文本 |
105+
| `deepseek-copilot.debugMode` | `minimal` | 诊断模式:`minimal` 仅上报 token 用量,`metadata` 输出隐私安全日志,`verbose` 将完整请求 dump 和 pipeline snapshot 写入扩展 global storage。完整 dump 可能包含敏感提示词文本、工具定义、文件片段和图片描述。使用 `DeepSeek: 打开请求 Dump 目录` 打开 dump 位置 |
106106
| `deepseek-copilot.visionModel` | *(自动)* | 用作视觉代理的 Copilot 模型 |
107107
| `deepseek-copilot.visionPrompt` | *(内置)* | 用于描述图片附件的提示词 |
108108

package.json

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@
7373
{
7474
"command": "deepseek-copilot.showLogs",
7575
"title": "%deepseek-copilot.command.showLogs%"
76+
},
77+
{
78+
"command": "deepseek-copilot.openRequestDumpsFolder",
79+
"title": "%deepseek-copilot.command.openRequestDumpsFolder%"
7680
}
7781
],
7882
"walkthroughs": [
@@ -117,11 +121,6 @@
117121
"minimum": 0,
118122
"description": "%deepseek-copilot.config.maxTokens.description%"
119123
},
120-
"deepseek-copilot.debug": {
121-
"type": "boolean",
122-
"default": false,
123-
"description": "%deepseek-copilot.config.debug.description%"
124-
},
125124
"deepseek-copilot.modelIdOverrides": {
126125
"type": "object",
127126
"default": {
@@ -151,6 +150,26 @@
151150
"editPresentation": "multilineText",
152151
"default": "Describe the visual contents of this image in detail, including any text, objects, people, or context that would be relevant for understanding it. Focus on factual visual elements.",
153152
"description": "%deepseek-copilot.config.visionPrompt.description%"
153+
},
154+
"deepseek-copilot.debugMode": {
155+
"type": "string",
156+
"default": "minimal",
157+
"enum": [
158+
"minimal",
159+
"metadata",
160+
"verbose"
161+
],
162+
"enumItemLabels": [
163+
"%deepseek-copilot.config.debugMode.minimal.label%",
164+
"%deepseek-copilot.config.debugMode.metadata.label%",
165+
"%deepseek-copilot.config.debugMode.verbose.label%"
166+
],
167+
"markdownEnumDescriptions": [
168+
"%deepseek-copilot.config.debugMode.minimal.description%",
169+
"%deepseek-copilot.config.debugMode.metadata.description%",
170+
"%deepseek-copilot.config.debugMode.verbose.description%"
171+
],
172+
"markdownDescription": "%deepseek-copilot.config.debugMode.description%"
154173
}
155174
}
156175
}

package.nls.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"deepseek-copilot.command.setVisionModel": "DeepSeek: Set Vision Proxy Model",
66
"deepseek-copilot.command.openSettings": "DeepSeek: Open Settings",
77
"deepseek-copilot.command.showLogs": "DeepSeek: Show Logs",
8+
"deepseek-copilot.command.openRequestDumpsFolder": "DeepSeek: Open Request Dumps Folder",
89
"deepseek-copilot.walkthrough.title": "DeepSeek V4 for Copilot Chat",
910
"deepseek-copilot.walkthrough.description": "Set up DeepSeek V4 models in Copilot Chat.",
1011
"deepseek-copilot.walkthrough.setApiKey.title": "Set your DeepSeek API key",
@@ -14,7 +15,13 @@
1415
"deepseek-copilot.config.title": "DeepSeek Copilot",
1516
"deepseek-copilot.config.baseUrl.description": "DeepSeek API base URL. Defaults to official DeepSeek API endpoint.",
1617
"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.",
17-
"deepseek-copilot.config.debug.description": "Enable diagnostic debug logging for troubleshooting. Logs privacy-preserving metadata such as request hashes, prefix overlap, tool schema changes, and missing reasoning_content. Does not log prompt text.",
18+
"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).",
19+
"deepseek-copilot.config.debugMode.minimal.label": "Minimal",
20+
"deepseek-copilot.config.debugMode.minimal.description": "Token usage only. No diagnostic logs or dumps.",
21+
"deepseek-copilot.config.debugMode.metadata.label": "Metadata",
22+
"deepseek-copilot.config.debugMode.metadata.description": "Privacy-safe metadata. Safe to share publicly. View with `DeepSeek: Show Logs`.",
23+
"deepseek-copilot.config.debugMode.verbose.label": "Verbose",
24+
"deepseek-copilot.config.debugMode.verbose.description": "⚠️ Contains sensitive prompt content. For local debugging only.",
1825
"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.",
1926
"deepseek-copilot.config.modelIdOverrides.deepseek-v4-flash.description": "API model ID for DeepSeek V4 Flash",
2027
"deepseek-copilot.config.modelIdOverrides.deepseek-v4-pro.description": "API model ID for DeepSeek V4 Pro",

package.nls.zh-cn.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"deepseek-copilot.command.setVisionModel": "DeepSeek: 设置视觉代理模型",
66
"deepseek-copilot.command.openSettings": "DeepSeek: 打开设置",
77
"deepseek-copilot.command.showLogs": "DeepSeek: 显示日志",
8+
"deepseek-copilot.command.openRequestDumpsFolder": "DeepSeek: 打开请求 Dump 目录",
89
"deepseek-copilot.walkthrough.title": "DeepSeek V4 for Copilot Chat",
910
"deepseek-copilot.walkthrough.description": "在 Copilot Chat 中配置 DeepSeek V4 模型。",
1011
"deepseek-copilot.walkthrough.setApiKey.title": "设置你的 DeepSeek API Key",
@@ -14,7 +15,13 @@
1415
"deepseek-copilot.config.title": "DeepSeek 助手",
1516
"deepseek-copilot.config.baseUrl.description": "DeepSeek API 基础 URL,默认为官方 DeepSeek API 端点。",
1617
"deepseek-copilot.config.maxTokens.description": "每次请求的最大输出 Token 数,设为 0 则不限制,可用于控制成本。",
17-
"deepseek-copilot.config.debug.description": "启用用于排查问题的诊断调试日志。日志只包含请求哈希、前缀重合度、工具定义变化、缺失 reasoning_content 等隐私保护后的元数据,不记录提示词原文。",
18+
"deepseek-copilot.config.debugMode.description": "控制 DeepSeek Copilot 写入的诊断信息量。无论此设置如何,token 用量始终上报给 Copilot。\n\n- **基本** — 仅上报 token 用量,不输出诊断日志或请求 dump。\n- **元数据** — 隐私安全的诊断元数据(请求哈希、前缀重合度、工具定义变更)。不含提示词原文,可安全附在公开 issue 中反馈问题。使用 [`DeepSeek: 显示日志`](command:deepseek-copilot.showLogs) 查看。\n- **详细** — 将完整请求体写入磁盘,供本地调试。**警告:包含敏感的提示词内容。** 使用 [`DeepSeek: 打开请求 Dump 目录`](command:deepseek-copilot.openRequestDumpsFolder) 浏览。",
19+
"deepseek-copilot.config.debugMode.minimal.label": "基本",
20+
"deepseek-copilot.config.debugMode.minimal.description": "仅上报 token 用量,不输出诊断日志或 dump。",
21+
"deepseek-copilot.config.debugMode.metadata.label": "元数据",
22+
"deepseek-copilot.config.debugMode.metadata.description": "隐私安全的元数据,可安全公开分享。使用 `DeepSeek: 显示日志` 查看。",
23+
"deepseek-copilot.config.debugMode.verbose.label": "详细",
24+
"deepseek-copilot.config.debugMode.verbose.description": "⚠️ 包含敏感的提示词内容,仅供本地调试。",
1825
"deepseek-copilot.config.modelIdOverrides.description": "覆盖各 DeepSeek 模型实际使用的 API 模型 ID,默认使用官方 DeepSeek ID,仅在对接不同模型名称的第三方 API 时需要修改。",
1926
"deepseek-copilot.config.modelIdOverrides.deepseek-v4-flash.description": "DeepSeek V4 Flash 的 API 模型 ID。",
2027
"deepseek-copilot.config.modelIdOverrides.deepseek-v4-pro.description": "DeepSeek V4 Pro 的 API 模型 ID。",

src/config.ts

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import vscode from 'vscode';
22
import { CONFIG_SECTION } from './consts';
33

4+
export type DebugMode = 'minimal' | 'metadata' | 'verbose';
5+
46
/**
57
* Get DeepSeek API base URL from settings.
68
* Falls back to the official endpoint when not configured.
@@ -34,10 +36,97 @@ export function getMaxTokens(): number | undefined {
3436
return value > 0 ? value : undefined;
3537
}
3638

39+
/**
40+
* Diagnostic mode. `verbose` also enables metadata logs.
41+
*
42+
* The legacy boolean `debug` setting is still read as a fallback so old
43+
* settings keep working even if migration cannot update every scope.
44+
*/
45+
export function getDebugMode(): DebugMode {
46+
const config = vscode.workspace.getConfiguration(CONFIG_SECTION);
47+
const mode = getConfiguredDebugMode(config);
48+
if (mode) return mode;
49+
50+
return config.get<boolean>('debug', false) ? 'metadata' : 'minimal';
51+
}
52+
3753
/**
3854
* Whether to log privacy-preserving diagnostic debug information.
3955
*/
4056
export function getDebugLoggingEnabled(): boolean {
41-
const config = vscode.workspace.getConfiguration(CONFIG_SECTION);
42-
return config.get<boolean>('debug', false);
57+
return getDebugMode() !== 'minimal';
58+
}
59+
60+
/**
61+
* Whether to write full DeepSeek request payloads to disk.
62+
*/
63+
export function getRequestDumpEnabled(): boolean {
64+
return getDebugMode() === 'verbose';
65+
}
66+
67+
/**
68+
* Migrate the legacy boolean `deepseek-copilot.debug` setting to `debugMode`.
69+
*
70+
* `debug: true` maps to `debugMode: metadata`; `debug: false` maps to the
71+
* default `minimal`, so it only needs cleanup.
72+
*/
73+
export async function migrateLegacyDebugSetting(): Promise<void> {
74+
await migrateLegacyDebugSettingAtScope(vscode.ConfigurationTarget.Global);
75+
if (vscode.workspace.workspaceFile || vscode.workspace.workspaceFolders?.length) {
76+
await migrateLegacyDebugSettingAtScope(vscode.ConfigurationTarget.Workspace);
77+
}
78+
}
79+
80+
function getConfiguredDebugMode(config: vscode.WorkspaceConfiguration): DebugMode | undefined {
81+
const mode = config.inspect<unknown>('debugMode');
82+
return normalizeDebugMode(mode?.workspaceValue) ?? normalizeDebugMode(mode?.globalValue);
83+
}
84+
85+
function normalizeDebugMode(value: unknown): DebugMode | undefined {
86+
if (value === 'minimal' || value === 'metadata' || value === 'verbose') {
87+
return value;
88+
}
89+
return undefined;
90+
}
91+
92+
async function migrateLegacyDebugSettingAtScope(
93+
target: vscode.ConfigurationTarget,
94+
resource?: vscode.Uri,
95+
): Promise<void> {
96+
const config = vscode.workspace.getConfiguration(CONFIG_SECTION, resource);
97+
const legacy = config.inspect<boolean>('debug');
98+
const mode = config.inspect<DebugMode>('debugMode');
99+
const legacyValue = getScopedValue(legacy, target);
100+
101+
if (legacyValue === undefined) {
102+
return;
103+
}
104+
105+
if (legacyValue === true && getScopedValue(mode, target) === undefined) {
106+
await config.update('debugMode', 'metadata', target);
107+
}
108+
await config.update('debug', undefined, target);
109+
}
110+
111+
function getScopedValue<T>(
112+
inspection:
113+
| {
114+
globalValue?: T;
115+
workspaceValue?: T;
116+
workspaceFolderValue?: T;
117+
}
118+
| undefined,
119+
target: vscode.ConfigurationTarget,
120+
): T | undefined {
121+
if (!inspection) {
122+
return undefined;
123+
}
124+
125+
if (target === vscode.ConfigurationTarget.Global) {
126+
return inspection.globalValue;
127+
}
128+
if (target === vscode.ConfigurationTarget.Workspace) {
129+
return inspection.workspaceValue;
130+
}
131+
return undefined;
43132
}

src/consts.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import type { ModelDefinition } from './types';
1010
/** VS Code configuration section prefix for all extension settings. */
1111
export const CONFIG_SECTION = 'deepseek-copilot';
1212

13+
// VS Code's internal LanguageModelChatMessageRole.System is not exposed in @types/vscode.
14+
export const LANGUAGE_MODEL_CHAT_SYSTEM_ROLE = 3;
15+
1316
// ---- Secret keys ----
1417

1518
/** SecretStorage key for the DeepSeek API key. */

src/extension.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,42 @@
11
import vscode from 'vscode';
2-
import { getDebugLoggingEnabled } from './config';
3-
import { WALKTHROUGH_ID, WELCOME_SHOWN_KEY } from './consts';
2+
import { getDebugMode, migrateLegacyDebugSetting } from './config';
3+
import { CONFIG_SECTION, WALKTHROUGH_ID, WELCOME_SHOWN_KEY } from './consts';
44
import { t } from './i18n';
55
import { logger } from './logger';
66
import { DeepSeekChatProvider } from './provider';
7+
import { ensureRequestDumpRoot } from './provider/dump';
78

89
let activeProvider: DeepSeekChatProvider | undefined;
910

1011
export async function activate(context: vscode.ExtensionContext) {
12+
try {
13+
await migrateLegacyDebugSetting();
14+
} catch (error) {
15+
logger.warn('Failed to migrate legacy debug setting', error);
16+
}
17+
1118
logger.info(
1219
`Activating extension version=${context.extension.packageJSON.version}` +
13-
` debug=${getDebugLoggingEnabled()}`,
20+
` debugMode=${getDebugMode()}`,
21+
);
22+
23+
// Log debugMode changes so users can trace when verbosity was toggled
24+
let currentDebugMode = getDebugMode();
25+
context.subscriptions.push(
26+
vscode.workspace.onDidChangeConfiguration((e) => {
27+
if (e.affectsConfiguration(`${CONFIG_SECTION}.debugMode`)) {
28+
const previous = currentDebugMode;
29+
currentDebugMode = getDebugMode();
30+
logger.info(`debugMode changed: ${previous} -> ${currentDebugMode}`);
31+
}
32+
}),
1433
);
1534

1635
context.subscriptions.push(
1736
vscode.commands.registerCommand('deepseek-copilot.showLogs', () => logger.show()),
37+
vscode.commands.registerCommand('deepseek-copilot.openRequestDumpsFolder', () =>
38+
openRequestDumpsFolder(context),
39+
),
1840
vscode.commands.registerCommand('deepseek-copilot.getApiKey', () =>
1941
vscode.env.openExternal(vscode.Uri.parse('https://platform.deepseek.com/api_keys')),
2042
),
@@ -72,6 +94,17 @@ export async function activate(context: vscode.ExtensionContext) {
7294
}
7395
}
7496

97+
async function openRequestDumpsFolder(context: vscode.ExtensionContext): Promise<void> {
98+
try {
99+
const root = await ensureRequestDumpRoot(context.globalStorageUri);
100+
logger.info(`Opening request dumps folder: ${root.toString(true)}`);
101+
await vscode.commands.executeCommand('revealFileInOS', root);
102+
} catch (error) {
103+
logger.warn('Failed to open request dumps folder', error);
104+
void vscode.window.showErrorMessage(t('extension.openRequestDumpsFolderFailed'));
105+
}
106+
}
107+
75108
async function showWelcomeIfNeeded(
76109
context: vscode.ExtensionContext,
77110
provider: DeepSeekChatProvider,

src/i18n.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const zh: Translations = {
5454
'extension.activateFailed': 'DeepSeek 激活失败,请运行 "DeepSeek: 显示日志" 查看详情。',
5555
'extension.deactivateFailed': 'DeepSeek 停用异常',
5656
'extension.welcomeFailed': '欢迎引导加载异常',
57+
'extension.openRequestDumpsFolderFailed':
58+
'打开请求 dump 目录失败,请运行 "DeepSeek: 显示日志" 查看详情。',
5759
};
5860

5961
const en: Translations = {
@@ -97,6 +99,8 @@ const en: Translations = {
9799
'extension.activateFailed': 'DeepSeek failed to activate. Run "DeepSeek: Show Logs" for details.',
98100
'extension.deactivateFailed': 'Failed to prepare DeepSeek provider for deactivate',
99101
'extension.welcomeFailed': 'Failed to show DeepSeek welcome prompt',
102+
'extension.openRequestDumpsFolderFailed':
103+
'Failed to open request dumps folder. Run "DeepSeek: Show Logs" for details.',
100104
};
101105

102106
/**

0 commit comments

Comments
 (0)