Skip to content

Commit cb91620

Browse files
authored
fix(provider): make preflight tool call ids provider-safe (#86)
Hash activator tool names in provider-owned preflight call IDs and use a provider-safe alphanumeric/underscore format so replayed Copilot chat history is less likely to be rejected when switching models. Also documents that the experimental tool-list stabilization setting can affect model switching within the same Copilot conversation.
1 parent eec6bde commit cb91620

4 files changed

Lines changed: 24 additions & 14 deletions

File tree

package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"deepseek-copilot.config.title": "DeepSeek Copilot",
1818
"deepseek-copilot.config.baseUrl.description": "DeepSeek API base URL. Defaults to official DeepSeek API endpoint.",
1919
"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.",
20-
"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\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.",
20+
"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.",
2121
"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).",
2222
"deepseek-copilot.config.debugMode.minimal.label": "Minimal",
2323
"deepseek-copilot.config.debugMode.minimal.description": "Token usage only. No diagnostic logs or dumps.",

package.nls.zh-cn.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"deepseek-copilot.config.title": "DeepSeek 助手",
1818
"deepseek-copilot.config.baseUrl.description": "DeepSeek API 基础 URL,默认为官方 DeepSeek API 端点。",
1919
"deepseek-copilot.config.maxTokens.description": "每次请求的最大输出 Token 数,设为 0 则不限制,可用于控制成本。",
20-
"deepseek-copilot.config.experimental.stabilizeToolList.description": "**实验性功能**:通过预先激活可用的工具来提升 DeepSeek 上下文缓存命中率。\n- 当已启用工具列表跨轮次变化时,这可能提高 DeepSeek 上下文缓存命中率;\n- 请求中将包含更多函数工具定义,input tokens 可能增加,虽然缓存命中的 input tokens 单价更低,但仍会计入用量。\n\n 通过 [配置工具](command:workbench.action.chat.configureTools) **查看和管理**工具列表:\n\n- 64 个或更少已启用工具时通常无需开启,除非工具列表仍在跨轮次变化;\n- 超过 128 个已启用工具时不建议开启:DeepSeek 单次 `tools` 请求最多支持 128 个 functions。考虑禁用部分不常用工具。",
20+
"deepseek-copilot.config.experimental.stabilizeToolList.description": "**实验性功能**:通过预先激活可用的工具来提升 DeepSeek 上下文缓存命中率。\n- 当已启用工具列表跨轮次变化时,这可能提高 DeepSeek 上下文缓存命中率;\n- 请求中将包含更多函数工具定义,input tokens 可能增加,虽然缓存命中的 input tokens 单价更低,但仍会计入用量;\n- 此设置可能会在当前 Copilot 对话历史中加入内部预检工具调用。在同一对话中切换到其他模型时,部分模型提供方可能无法正确重放这段历史;如果切换模型后请求异常,请新建对话后重试。\n\n 通过 [配置工具](command:workbench.action.chat.configureTools) **查看和管理**工具列表:\n\n- 64 个或更少已启用工具时通常无需开启,除非工具列表仍在跨轮次变化;\n- 超过 128 个已启用工具时不建议开启:DeepSeek 单次 `tools` 请求最多支持 128 个 functions。考虑禁用部分不常用工具。",
2121
"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) 浏览。",
2222
"deepseek-copilot.config.debugMode.minimal.label": "基本",
2323
"deepseek-copilot.config.debugMode.minimal.description": "仅上报 token 用量,不输出诊断日志或 dump。",

src/provider/tools/consts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
export const DEEPSEEK_TOOLS_LIMIT = 128;
44

55
export const ACTIVATE_TOOL_PREFIX = 'activate_';
6-
export const PREFLIGHT_ACTIVATE_CALL_ID_PREFIX = 'deepseek-preflight-activate:';
6+
export const PREFLIGHT_ACTIVATE_CALL_ID_PREFIX = 'deepseek_preflight_activate_';
77
export const MAX_PREFLIGHT_ROUNDS_PER_USER_REQUEST = 3;
88

99
export const TOOL_DRIFT_NOTICE_START = '[deepseek-copilot-tool-drift-notice-start]: #';

src/provider/tools/preflight.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import { createHash } from 'crypto';
12
import vscode from 'vscode';
23
import { ACTIVATE_TOOL_PREFIX, PREFLIGHT_ACTIVATE_CALL_ID_PREFIX } from './consts';
34

5+
const PREFLIGHT_TOOL_NAME_HASH_LENGTH = 32;
6+
const PREFLIGHT_CALL_ID_SEPARATOR = '_';
7+
48
export interface ActivatePreflightInspection {
59
rounds: number;
610
calledActivatorNames: string[];
@@ -64,7 +68,12 @@ export function filterPreflightControlFlow(
6468
}
6569

6670
export function createPreflightToolCallId(round: number, toolName: string): string {
67-
return `${PREFLIGHT_ACTIVATE_CALL_ID_PREFIX}${round}:${encodeURIComponent(toolName)}`;
71+
// Keep IDs short and within the conservative alnum/_ set for cross-provider replay.
72+
const toolNameHash = createHash('sha256')
73+
.update(toolName)
74+
.digest('hex')
75+
.slice(0, PREFLIGHT_TOOL_NAME_HASH_LENGTH);
76+
return `${PREFLIGHT_ACTIVATE_CALL_ID_PREFIX}${round}${PREFLIGHT_CALL_ID_SEPARATOR}${toolNameHash}`;
6877
}
6978

7079
function collectActivateToolNames(
@@ -109,7 +118,14 @@ function isHumanUserMessagePart(part: unknown): boolean {
109118

110119
function parsePreflightPart(part: unknown): { round: number; toolName?: string } | undefined {
111120
if (part instanceof vscode.LanguageModelToolCallPart) {
112-
return parsePreflightToolCallId(part.callId) ?? undefined;
121+
const parsed = parsePreflightToolCallId(part.callId);
122+
if (!parsed) {
123+
return undefined;
124+
}
125+
return {
126+
round: parsed.round,
127+
toolName: part.name,
128+
};
113129
}
114130
if (part instanceof vscode.LanguageModelToolResultPart) {
115131
return parsePreflightToolCallId(part.callId) ?? undefined;
@@ -129,15 +145,13 @@ function isEmptyTextPart(part: unknown): boolean {
129145
return part instanceof vscode.LanguageModelTextPart && part.value.length === 0;
130146
}
131147

132-
function parsePreflightToolCallId(
133-
callId: string,
134-
): { round: number; toolName?: string } | undefined {
148+
function parsePreflightToolCallId(callId: string): { round: number } | undefined {
135149
if (!callId.startsWith(PREFLIGHT_ACTIVATE_CALL_ID_PREFIX)) {
136150
return undefined;
137151
}
138152

139153
const value = callId.slice(PREFLIGHT_ACTIVATE_CALL_ID_PREFIX.length);
140-
const separatorIndex = value.indexOf(':');
154+
const separatorIndex = value.indexOf(PREFLIGHT_CALL_ID_SEPARATOR);
141155
if (separatorIndex < 0) {
142156
return undefined;
143157
}
@@ -147,9 +161,5 @@ function parsePreflightToolCallId(
147161
return undefined;
148162
}
149163

150-
try {
151-
return { round, toolName: decodeURIComponent(value.slice(separatorIndex + 1)) };
152-
} catch {
153-
return { round };
154-
}
164+
return { round };
155165
}

0 commit comments

Comments
 (0)