Skip to content

Commit 95e2d77

Browse files
committed
Merge branch 'implement-read-image-tool'
2 parents 13bca31 + 020d8f9 commit 95e2d77

31 files changed

Lines changed: 1611 additions & 161 deletions

package-lock.json

Lines changed: 534 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"ink-gradient": "^4.0.1",
4242
"react": "^19.2.5",
4343
"read-package-up": "^12.0.0",
44+
"sharp": "^0.34.5",
4445
"yargs": "^18.0.0"
4546
},
4647
"devDependencies": {

packages/cli/src/common/update-check.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as os from "os";
55
import * as path from "path";
66
import { render, type Instance } from "ink";
77
import { UpdatePrompt, type UpdatePromptChoice } from "../ui";
8-
import { killProcessTree } from "@vegamo/deepcode-core";
8+
import { killProcessTree, TENCENT_MIRROR_REGISTRY } from "@vegamo/deepcode-core";
99
import type { PackageJson } from "../utils/package";
1010

1111
type UpdateState = {
@@ -21,7 +21,6 @@ type UpdateState = {
2121
const UPDATE_STATE_FILE = "update-check.json";
2222
const NPM_VIEW_TIMEOUT_MS = 5000;
2323
const MAX_NPM_VIEW_OUTPUT_CHARS = 64 * 1024;
24-
const TENCENT_MIRROR_REGISTRY = "https://mirrors.cloud.tencent.com/npm/";
2524
export const UPDATE_SUCCESS_MESSAGE = "🎉 Update ran successfully! Please restart Deep Code.";
2625

2726
export async function promptForPendingUpdate(packageInfo: PackageJson): Promise<{ installed: boolean }> {

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"gray-matter": "^4.0.3",
3434
"ignore": "^7.0.5",
3535
"openai": "^6.35.0",
36+
"sharp": "^0.34.5",
3637
"undici": "^7.29.0",
3738
"zod": "^4.4.3"
3839
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const TENCENT_MIRROR_REGISTRY = "https://mirrors.cloud.tencent.com/npm/";

packages/core/src/common/permissions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,12 @@ export function describeToolPermissionRequest(options: {
219219
const name = options.toolCall.function.name;
220220
const args = parseToolArgumentsForPermissions(options.toolCall.function.arguments);
221221

222-
if (name === "read" || name === "Read") {
222+
if (name === "read" || name === "Read" || name === "ReadImage") {
223223
const filePath = typeof args.file_path === "string" ? args.file_path : "";
224224
return {
225225
toolCallId: options.toolCall.id,
226226
name,
227-
command: formatToolPathCommand("read", filePath),
227+
command: formatToolPathCommand(name === "ReadImage" ? "read-image" : "read", filePath),
228228
scopes:
229229
filePath && !isPathInAnyDirectory(options.projectRoot, filePath, options.readPermissionExemptPaths)
230230
? [isPathInProject(options.projectRoot, filePath) ? "read-in-cwd" : "read-out-cwd"]

packages/core/src/common/tool-types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type OpenAI from "openai";
2+
import type sharp from "sharp";
23
import type { ReasoningEffort } from "../settings";
34

45
export type CreateOpenAIClient = () => {
@@ -28,11 +29,14 @@ export type ToolCall = {
2829

2930
export type PluginRateLimitedTool = "UnderstandImage" | "WebSearch";
3031

32+
export type SharpLoader = () => Promise<typeof sharp>;
33+
3134
export type ToolExecutionContext = {
3235
sessionId: string;
3336
projectRoot: string;
3437
toolCall: ToolCall;
3538
createOpenAIClient?: CreateOpenAIClient;
39+
loadSharp?: SharpLoader;
3640
onProcessStart?: (processId: string | number, command: string) => void;
3741
onProcessExit?: (processId: string | number) => void;
3842
onProcessStdout?: (processId: string | number, chunk: string) => void;

packages/core/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,13 @@ export type {
8080
BackgroundProcessCompletion,
8181
ToolExecutionFollowUpMessage,
8282
PluginRateLimitedTool,
83+
SharpLoader,
8384
} from "./common/tool-types";
8485

8586
// Tool handlers
8687
export { handleBashTool, clearSessionWorkingDir } from "./tools/bash-handler";
8788
export { handleReadTool } from "./tools/read-handler";
89+
export { handleReadImageTool } from "./tools/read-image-handler";
8890
export { handleWriteTool } from "./tools/write-handler";
8991
export { handleEditTool } from "./tools/edit-handler";
9092
export { handleUpdatePlanTool } from "./tools/update-plan-handler";
@@ -104,6 +106,7 @@ export { readTextFileWithMetadata, writeTextFile, buildDiffPreview, ensureParent
104106
export { normalizeFilePath, getSnippet, clearSessionState, recordFileState, getFileState } from "./common/state";
105107
export { GitFileHistory } from "./common/file-history";
106108
export { killProcessTree } from "./common/process-tree";
109+
export { TENCENT_MIRROR_REGISTRY } from "./common/npm-registry";
107110
export { launchNotifyScript } from "./common/notify";
108111
export { reportNewPrompt } from "./common/telemetry";
109112
export { DEEPSEEK_V4_MODELS, supportsMultimodal, defaultsToThinkingMode } from "./common/model-capabilities";

packages/core/src/prompt.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ export function getTools(_options: PromptToolOptions = {}, externalTools: ToolDe
584584
type: "function",
585585
function: {
586586
name: "read",
587-
description: "Read files from the filesystem (text, images, notebooks).",
587+
description: "Read text files and notebooks from the filesystem. Image files require a dedicated image tool.",
588588
parameters: {
589589
type: "object",
590590
properties: {
@@ -696,7 +696,27 @@ export function getTools(_options: PromptToolOptions = {}, externalTools: ToolDe
696696
},
697697
});
698698

699-
if (!supportsMultimodal(_options.model ?? "", _options.multimodal)) {
699+
if (supportsMultimodal(_options.model ?? "", _options.multimodal)) {
700+
tools.push({
701+
type: "function",
702+
function: {
703+
name: "ReadImage",
704+
description:
705+
"Read a PNG, JPEG, WebP, or GIF file and return the image itself. Large images are validated and downscaled before the next model request.",
706+
parameters: {
707+
type: "object",
708+
properties: {
709+
file_path: {
710+
type: "string",
711+
description: "The absolute path of the PNG, JPEG, WebP, or GIF image to read.",
712+
},
713+
},
714+
required: ["file_path"],
715+
additionalProperties: false,
716+
},
717+
},
718+
});
719+
} else {
700720
tools.push({
701721
type: "function",
702722
function: {

packages/core/src/session.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
type ProcessTimeoutControl,
2626
type ProcessTimeoutInfo,
2727
type PluginRateLimitedTool,
28+
type SharpLoader,
2829
type ToolCallExecution,
2930
type ToolExecutionHooks,
3031
type ToolExecutionFollowUpMessage,
@@ -353,6 +354,7 @@ export type SessionManagerOptions = {
353354
onLlmStreamProgress?: (progress: LlmStreamProgress) => void;
354355
onMcpStatusChanged?: () => void;
355356
onProcessStdout?: (pid: number, chunk: string) => void;
357+
loadSharp?: SharpLoader;
356358
nonInteractive?: boolean;
357359
};
358360

@@ -404,7 +406,7 @@ export class SessionManager {
404406
this.onMcpStatusChanged = options.onMcpStatusChanged;
405407
this.onProcessStdout = options.onProcessStdout;
406408
this.nonInteractive = options.nonInteractive === true;
407-
this.toolExecutor = new ToolExecutor(this.projectRoot, this.createOpenAIClient, this.mcpManager);
409+
this.toolExecutor = new ToolExecutor(this.projectRoot, this.createOpenAIClient, this.mcpManager, options.loadSharp);
408410
this.mcpManager.prepare(this.getResolvedSettings().mcpServers);
409411
this.messageConverter = new OpenAIMessageConverter({
410412
renderInitPrompt: () => this.renderInitCommandPrompt(),
@@ -2869,7 +2871,7 @@ ${agentInstructions}
28692871

28702872
const value = args[firstKey];
28712873
const text = typeof value === "string" ? value : JSON.stringify(value);
2872-
if (toolName === "read" && text.startsWith(this.projectRoot)) {
2874+
if ((toolName === "read" || toolName === "ReadImage") && text.startsWith(this.projectRoot)) {
28732875
return text.slice(this.projectRoot.length).replace(/^[\\/]/, "");
28742876
}
28752877
return text;

0 commit comments

Comments
 (0)