Skip to content

Commit c92e429

Browse files
committed
refactor: refactor model capability management and improve multimodal model inspection
1 parent 393e71b commit c92e429

8 files changed

Lines changed: 155 additions & 46 deletions

File tree

src/common/model-capabilities.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const DEEPSEEK_V4_MODELS = new Set(["deepseek-v4-flash", "deepseek-v4-pro"]);
2+
3+
export const NON_MULTIMODAL_MODELS = new Set([
4+
"deepseek-v4-pro",
5+
"deepseek-v4-flash",
6+
"deepseek-chat",
7+
"deepseek-reasoner",
8+
]);
9+
10+
export function defaultsToThinkingMode(model: string): boolean {
11+
return DEEPSEEK_V4_MODELS.has(model);
12+
}
13+
14+
export function supportsMultimodal(model: string): boolean {
15+
return !NON_MULTIMODAL_MODELS.has(model.trim());
16+
}

src/model-capabilities.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/prompt.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import * as fs from "fs";
33
import * as os from "os";
44
import * as path from "path";
55
import { fileURLToPath } from "url";
6+
import ejs from "ejs";
67
import type { SessionMessage } from "./session";
78
import { findGitBashPath, resolveShellPath } from "./common/shell-utils";
9+
import { supportsMultimodal } from "./common/model-capabilities";
810

911
export const AGENT_DRIFT_GUARD_SKILL = `
1012
---
@@ -248,23 +250,28 @@ const SYSTEM_PROMPT_BASE = `你是名叫Deep Code的交互式CLI工具,帮助
248250
重要:严禁编造任何非编程相关的 URL。对于编程链接,仅限使用:1) 用户提供的上下文;2) 你确定的官方文档主域名。在输出前,必须自查该链接是否存在于你的上下文记忆中;若不存在,请明确说明无法提供。`;
249251

250252
type PromptToolOptions = {
253+
model?: string;
251254
webSearchEnabled?: boolean;
252255
};
253256

254-
function readToolDocs(extensionRoot: string, _options: PromptToolOptions = {}): string {
257+
function readToolDocs(extensionRoot: string, options: PromptToolOptions = {}): string {
255258
const toolsDir = path.join(extensionRoot, "templates", "tools");
256259
if (!fs.existsSync(toolsDir)) {
257260
return "";
258261
}
259262

260263
const entries = fs.readdirSync(toolsDir);
261264
const docs = entries
262-
.filter((entry) => entry.endsWith(".md"))
265+
.filter((entry) => entry.endsWith(".md") || entry.endsWith(".md.ejs"))
263266
.sort()
264267
.map((entry) => {
265268
const fullPath = path.join(toolsDir, entry);
266269
try {
267-
return fs.readFileSync(fullPath, "utf8").trim();
270+
const template = fs.readFileSync(fullPath, "utf8");
271+
const content = entry.endsWith(".ejs")
272+
? ejs.render(template, { supportsMultimodal: supportsMultimodal(options.model ?? "") })
273+
: template;
274+
return content.trim();
268275
} catch {
269276
return "";
270277
}

src/session.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import ejs from "ejs";
88
import type { ChatCompletionMessageParam, ChatCompletionContentPart } from "openai/resources/chat/completions";
99
import { launchNotifyScript } from "./notify";
1010
import { buildThinkingRequestOptions } from "./openai-thinking";
11-
import { DEEPSEEK_V4_MODELS } from "./model-capabilities";
11+
import { DEEPSEEK_V4_MODELS, supportsMultimodal } from "./common/model-capabilities";
1212
import { getCompactPrompt, getSystemPrompt, getTools, AGENT_DRIFT_GUARD_SKILL, type ToolDefinition } from "./prompt";
1313
import { ToolExecutor, type CreateOpenAIClient } from "./tools/executor";
1414
import { McpManager } from "./mcp/mcp-manager";
@@ -156,7 +156,7 @@ export type SkillInfo = {
156156
type SessionManagerOptions = {
157157
projectRoot: string;
158158
createOpenAIClient: CreateOpenAIClient;
159-
getResolvedSettings: () => { webSearchTool?: string; mcpServers?: Record<string, McpServerConfig> };
159+
getResolvedSettings: () => { model: string; webSearchTool?: string; mcpServers?: Record<string, McpServerConfig> };
160160
renderMarkdown: (text: string) => string;
161161
onAssistantMessage: (message: SessionMessage, shouldConnect: boolean) => void;
162162
onSessionEntryUpdated?: (entry: SessionEntry) => void;
@@ -175,7 +175,11 @@ export type LlmStreamProgress = {
175175
export class SessionManager {
176176
private readonly projectRoot: string;
177177
private readonly createOpenAIClient: CreateOpenAIClient;
178-
private readonly getResolvedSettings: () => { webSearchTool?: string; mcpServers?: Record<string, McpServerConfig> };
178+
private readonly getResolvedSettings: () => {
179+
model: string;
180+
webSearchTool?: string;
181+
mcpServers?: Record<string, McpServerConfig>;
182+
};
179183
private readonly onAssistantMessage: (message: SessionMessage, shouldConnect: boolean) => void;
180184
private readonly onSessionEntryUpdated?: (entry: SessionEntry) => void;
181185
private readonly onLlmStreamProgress?: (progress: LlmStreamProgress) => void;
@@ -1017,7 +1021,7 @@ ${skillMd}
10171021
await this.compactSession(sessionId, sessionController.signal);
10181022
}
10191023

1020-
const messages = this.buildOpenAIMessages(this.listSessionMessages(sessionId), thinkingEnabled);
1024+
const messages = this.buildOpenAIMessages(this.listSessionMessages(sessionId), thinkingEnabled, model);
10211025
const thinkingOptions = buildThinkingRequestOptions(thinkingEnabled, baseURL, reasoningEffort);
10221026
const response = await this.createChatCompletionStream(
10231027
client,
@@ -1208,8 +1212,9 @@ ${skillMd}
12081212
this.saveSessionMessages(sessionId, sessionMessages);
12091213
}
12101214

1211-
private getPromptToolOptions(): { webSearchEnabled: boolean } {
1215+
private getPromptToolOptions(): { model: string; webSearchEnabled: boolean } {
12121216
return {
1217+
model: this.getResolvedSettings().model,
12131218
webSearchEnabled: true,
12141219
};
12151220
}
@@ -1678,7 +1683,11 @@ ${skillMd}
16781683
return { waitingForUser };
16791684
}
16801685

1681-
private buildOpenAIMessages(messages: SessionMessage[], thinkingEnabled: boolean): ChatCompletionMessageParam[] {
1686+
private buildOpenAIMessages(
1687+
messages: SessionMessage[],
1688+
thinkingEnabled: boolean,
1689+
model: string
1690+
): ChatCompletionMessageParam[] {
16821691
const activeMessages = messages.filter((message) => !message.compacted);
16831692
const toolPairings = this.pairToolMessages(activeMessages);
16841693
const openAIMessages: ChatCompletionMessageParam[] = [];
@@ -1689,7 +1698,7 @@ ${skillMd}
16891698
continue;
16901699
}
16911700

1692-
openAIMessages.push(this.sessionMessageToOpenAIMessage(message, thinkingEnabled));
1701+
openAIMessages.push(this.sessionMessageToOpenAIMessage(message, thinkingEnabled, model));
16931702

16941703
const toolCalls = this.getAssistantToolCalls(message);
16951704
if (toolCalls.length === 0) {
@@ -1704,7 +1713,9 @@ ${skillMd}
17041713

17051714
const pairedToolIndex = toolPairings.get(this.buildToolPairingKey(index, toolCallIndex));
17061715
if (pairedToolIndex != null) {
1707-
openAIMessages.push(this.sessionMessageToOpenAIMessage(activeMessages[pairedToolIndex], thinkingEnabled));
1716+
openAIMessages.push(
1717+
this.sessionMessageToOpenAIMessage(activeMessages[pairedToolIndex], thinkingEnabled, model)
1718+
);
17081719
continue;
17091720
}
17101721

@@ -1715,7 +1726,11 @@ ${skillMd}
17151726
return openAIMessages;
17161727
}
17171728

1718-
private sessionMessageToOpenAIMessage(message: SessionMessage, thinkingEnabled: boolean): ChatCompletionMessageParam {
1729+
private sessionMessageToOpenAIMessage(
1730+
message: SessionMessage,
1731+
thinkingEnabled: boolean,
1732+
model: string
1733+
): ChatCompletionMessageParam {
17191734
const content = this.renderOpenAIMessageContent(message);
17201735
const base: ChatCompletionMessageParam = {
17211736
role: message.role,
@@ -1748,7 +1763,7 @@ ${skillMd}
17481763
const params = Array.isArray(message.contentParams) ? message.contentParams : [message.contentParams];
17491764
for (const param of params) {
17501765
const part = param as ChatCompletionContentPart;
1751-
if (part && part.type !== "image_url") {
1766+
if (part && (part.type !== "image_url" || supportsMultimodal(model))) {
17521767
contentParts.push(part);
17531768
}
17541769
}

src/settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defaultsToThinkingMode } from "./model-capabilities";
1+
import { defaultsToThinkingMode } from "./common/model-capabilities";
22

33
export type DeepcodingEnv = Record<string, string | undefined> & {
44
MODEL?: string;

src/tests/prompt.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ test("getSystemPrompt always includes WebSearch docs", () => {
1717
assert.equal(prompt.includes("## WebSearch"), true);
1818
});
1919

20+
test("getSystemPrompt renders Read docs for non-multimodal models", () => {
21+
const prompt = getSystemPrompt("/tmp/project", { model: "deepseek-chat" });
22+
assert.equal(prompt.includes("the current model is not multimodal"), true);
23+
assert.equal(prompt.includes("the contents are presented visually"), false);
24+
});
25+
2026
test("runtime prompt assets live under templates", () => {
2127
assert.equal(fs.existsSync(path.join(repoRoot, "templates", "tools", "web-search.md")), true);
28+
assert.equal(fs.existsSync(path.join(repoRoot, "templates", "tools", "read.md.ejs")), true);
2229
assert.equal(fs.existsSync(path.join(repoRoot, "templates", "prompts", "init_command.md.ejs")), true);
30+
assert.equal(fs.existsSync(path.join(repoRoot, "templates", "tools", "read.md")), false);
2331
assert.equal(fs.existsSync(path.join(repoRoot, "docs", "tools")), false);
2432
assert.equal(fs.existsSync(path.join(repoRoot, "docs", "prompts")), false);
2533
});

0 commit comments

Comments
 (0)