feat: add codex image tool (#650)
This commit is contained in:
@@ -40,6 +40,7 @@ export function getDisplayableToolReturn(content: ToolReturnContent): string {
|
|||||||
const PARALLEL_SAFE_TOOLS = new Set([
|
const PARALLEL_SAFE_TOOLS = new Set([
|
||||||
// === Anthropic toolset (default) ===
|
// === Anthropic toolset (default) ===
|
||||||
"Read",
|
"Read",
|
||||||
|
"view_image",
|
||||||
"Grep",
|
"Grep",
|
||||||
"Glob",
|
"Glob",
|
||||||
|
|
||||||
|
|||||||
@@ -248,14 +248,14 @@ export function formatArgsDisplay(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read tools: show file path + any other useful args (limit, offset)
|
// Read tools: show file path + any other useful args (limit, offset)
|
||||||
if (isFileReadTool(toolName) && parsed.file_path) {
|
if (isFileReadTool(toolName) && (parsed.file_path || parsed.path)) {
|
||||||
const filePath = String(parsed.file_path);
|
const filePath = String(parsed.file_path || parsed.path);
|
||||||
const relativePath = formatDisplayPath(filePath);
|
const relativePath = formatDisplayPath(filePath);
|
||||||
|
|
||||||
// Collect other non-hidden args
|
// Collect other non-hidden args
|
||||||
const otherArgs: string[] = [];
|
const otherArgs: string[] = [];
|
||||||
for (const [k, v] of Object.entries(parsed)) {
|
for (const [k, v] of Object.entries(parsed)) {
|
||||||
if (k === "file_path") continue;
|
if (k === "file_path" || k === "path") continue;
|
||||||
if (v === undefined || v === null) continue;
|
if (v === undefined || v === null) continue;
|
||||||
if (typeof v === "boolean" || typeof v === "number") {
|
if (typeof v === "boolean" || typeof v === "number") {
|
||||||
otherArgs.push(`${k}: ${v}`);
|
otherArgs.push(`${k}: ${v}`);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export function getDisplayToolName(rawName: string): string {
|
|||||||
if (rawName === "write") return "Write";
|
if (rawName === "write") return "Write";
|
||||||
if (rawName === "edit" || rawName === "multi_edit") return "Update";
|
if (rawName === "edit" || rawName === "multi_edit") return "Update";
|
||||||
if (rawName === "read") return "Read";
|
if (rawName === "read") return "Read";
|
||||||
|
if (rawName === "view_image" || rawName === "ViewImage") return "View Image";
|
||||||
if (rawName === "bash") return "Bash";
|
if (rawName === "bash") return "Bash";
|
||||||
if (rawName === "grep" || rawName === "Grep") return "Search";
|
if (rawName === "grep" || rawName === "Grep") return "Search";
|
||||||
if (rawName === "glob" || rawName === "Glob") return "Glob";
|
if (rawName === "glob" || rawName === "Glob") return "Glob";
|
||||||
@@ -180,6 +181,8 @@ export function isFileReadTool(name: string): boolean {
|
|||||||
return (
|
return (
|
||||||
name === "read" ||
|
name === "read" ||
|
||||||
name === "Read" ||
|
name === "Read" ||
|
||||||
|
name === "view_image" ||
|
||||||
|
name === "ViewImage" ||
|
||||||
name === "ReadFile" ||
|
name === "ReadFile" ||
|
||||||
name === "read_file" ||
|
name === "read_file" ||
|
||||||
name === "read_file_gemini" ||
|
name === "read_file_gemini" ||
|
||||||
|
|||||||
8
src/tools/descriptions/ViewImage.md
Normal file
8
src/tools/descriptions/ViewImage.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# ViewImage
|
||||||
|
|
||||||
|
Attach a local image file to the conversation context for this turn.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
- The `path` parameter must be an absolute path to a local image file
|
||||||
|
- Supported formats: PNG, JPG, JPEG, GIF, WEBP, BMP
|
||||||
|
- Large images are automatically resized to fit API limits
|
||||||
39
src/tools/impl/ViewImage.ts
Normal file
39
src/tools/impl/ViewImage.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import * as path from "node:path";
|
||||||
|
import { read, type ToolReturnContent } from "./Read";
|
||||||
|
import { validateRequiredParams } from "./validation.js";
|
||||||
|
|
||||||
|
interface ViewImageArgs {
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IMAGE_EXTENSIONS = new Set([
|
||||||
|
".png",
|
||||||
|
".jpg",
|
||||||
|
".jpeg",
|
||||||
|
".gif",
|
||||||
|
".webp",
|
||||||
|
".bmp",
|
||||||
|
]);
|
||||||
|
|
||||||
|
function isImageFile(filePath: string): boolean {
|
||||||
|
const ext = path.extname(filePath).toLowerCase();
|
||||||
|
return IMAGE_EXTENSIONS.has(ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function view_image(
|
||||||
|
args: ViewImageArgs,
|
||||||
|
): Promise<{ content: ToolReturnContent }> {
|
||||||
|
validateRequiredParams(args, ["path"], "view_image");
|
||||||
|
|
||||||
|
const userCwd = process.env.USER_CWD || process.cwd();
|
||||||
|
const resolvedPath = path.isAbsolute(args.path)
|
||||||
|
? args.path
|
||||||
|
: path.resolve(userCwd, args.path);
|
||||||
|
|
||||||
|
if (!isImageFile(resolvedPath)) {
|
||||||
|
throw new Error(`Unsupported image file type: ${resolvedPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await read({ file_path: resolvedPath });
|
||||||
|
return { content: result.content };
|
||||||
|
}
|
||||||
@@ -82,6 +82,7 @@ export const OPENAI_DEFAULT_TOOLS: ToolName[] = [
|
|||||||
"grep_files",
|
"grep_files",
|
||||||
"apply_patch",
|
"apply_patch",
|
||||||
"update_plan",
|
"update_plan",
|
||||||
|
"view_image",
|
||||||
"Skill",
|
"Skill",
|
||||||
"Task",
|
"Task",
|
||||||
];
|
];
|
||||||
@@ -112,6 +113,7 @@ export const OPENAI_PASCAL_TOOLS: ToolName[] = [
|
|||||||
"ShellCommand",
|
"ShellCommand",
|
||||||
"Shell",
|
"Shell",
|
||||||
"ReadFile",
|
"ReadFile",
|
||||||
|
"view_image",
|
||||||
"ListDir",
|
"ListDir",
|
||||||
"GrepFiles",
|
"GrepFiles",
|
||||||
"ApplyPatch",
|
"ApplyPatch",
|
||||||
@@ -151,6 +153,7 @@ const TOOL_PERMISSIONS: Record<ToolName, { requiresApproval: boolean }> = {
|
|||||||
LS: { requiresApproval: false },
|
LS: { requiresApproval: false },
|
||||||
MultiEdit: { requiresApproval: true },
|
MultiEdit: { requiresApproval: true },
|
||||||
Read: { requiresApproval: false },
|
Read: { requiresApproval: false },
|
||||||
|
view_image: { requiresApproval: false },
|
||||||
ReadLSP: { requiresApproval: false },
|
ReadLSP: { requiresApproval: false },
|
||||||
Skill: { requiresApproval: false },
|
Skill: { requiresApproval: false },
|
||||||
Task: { requiresApproval: true },
|
Task: { requiresApproval: true },
|
||||||
|
|||||||
12
src/tools/schemas/ViewImage.json
Normal file
12
src/tools/schemas/ViewImage.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The absolute path to the image file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["path"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import SkillDescription from "./descriptions/Skill.md";
|
|||||||
import TaskDescription from "./descriptions/Task.md";
|
import TaskDescription from "./descriptions/Task.md";
|
||||||
import TodoWriteDescription from "./descriptions/TodoWrite.md";
|
import TodoWriteDescription from "./descriptions/TodoWrite.md";
|
||||||
import UpdatePlanDescription from "./descriptions/UpdatePlan.md";
|
import UpdatePlanDescription from "./descriptions/UpdatePlan.md";
|
||||||
|
import ViewImageDescription from "./descriptions/ViewImage.md";
|
||||||
import WriteDescription from "./descriptions/Write.md";
|
import WriteDescription from "./descriptions/Write.md";
|
||||||
import WriteFileGeminiDescription from "./descriptions/WriteFileGemini.md";
|
import WriteFileGeminiDescription from "./descriptions/WriteFileGemini.md";
|
||||||
import WriteTodosGeminiDescription from "./descriptions/WriteTodosGemini.md";
|
import WriteTodosGeminiDescription from "./descriptions/WriteTodosGemini.md";
|
||||||
@@ -63,6 +64,7 @@ import { skill } from "./impl/Skill";
|
|||||||
import { task } from "./impl/Task";
|
import { task } from "./impl/Task";
|
||||||
import { todo_write } from "./impl/TodoWrite";
|
import { todo_write } from "./impl/TodoWrite";
|
||||||
import { update_plan } from "./impl/UpdatePlan";
|
import { update_plan } from "./impl/UpdatePlan";
|
||||||
|
import { view_image } from "./impl/ViewImage";
|
||||||
import { write } from "./impl/Write";
|
import { write } from "./impl/Write";
|
||||||
import { write_file_gemini } from "./impl/WriteFileGemini";
|
import { write_file_gemini } from "./impl/WriteFileGemini";
|
||||||
import { write_todos } from "./impl/WriteTodosGemini";
|
import { write_todos } from "./impl/WriteTodosGemini";
|
||||||
@@ -97,6 +99,7 @@ import SkillSchema from "./schemas/Skill.json";
|
|||||||
import TaskSchema from "./schemas/Task.json";
|
import TaskSchema from "./schemas/Task.json";
|
||||||
import TodoWriteSchema from "./schemas/TodoWrite.json";
|
import TodoWriteSchema from "./schemas/TodoWrite.json";
|
||||||
import UpdatePlanSchema from "./schemas/UpdatePlan.json";
|
import UpdatePlanSchema from "./schemas/UpdatePlan.json";
|
||||||
|
import ViewImageSchema from "./schemas/ViewImage.json";
|
||||||
import WriteSchema from "./schemas/Write.json";
|
import WriteSchema from "./schemas/Write.json";
|
||||||
import WriteFileGeminiSchema from "./schemas/WriteFileGemini.json";
|
import WriteFileGeminiSchema from "./schemas/WriteFileGemini.json";
|
||||||
import WriteTodosGeminiSchema from "./schemas/WriteTodosGemini.json";
|
import WriteTodosGeminiSchema from "./schemas/WriteTodosGemini.json";
|
||||||
@@ -170,6 +173,11 @@ const toolDefinitions = {
|
|||||||
description: ReadDescription.trim(),
|
description: ReadDescription.trim(),
|
||||||
impl: read as unknown as ToolImplementation,
|
impl: read as unknown as ToolImplementation,
|
||||||
},
|
},
|
||||||
|
view_image: {
|
||||||
|
schema: ViewImageSchema,
|
||||||
|
description: ViewImageDescription.trim(),
|
||||||
|
impl: view_image as unknown as ToolImplementation,
|
||||||
|
},
|
||||||
// LSP-enhanced Read - used when LETTA_ENABLE_LSP is set
|
// LSP-enhanced Read - used when LETTA_ENABLE_LSP is set
|
||||||
ReadLSP: {
|
ReadLSP: {
|
||||||
schema: ReadLSPSchema,
|
schema: ReadLSPSchema,
|
||||||
|
|||||||
Reference in New Issue
Block a user