Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 29 additions & 32 deletions src/main.mts
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,25 @@ import {
import { handleChildSIGINT } from "./tools/task-state.mjs";
import { bashCommandTool } from "./tools/bash-commad.mjs";
import { nodejsScriptTool } from "./tools/nodejs-script.mjs";
import { filesWriteTool } from "./tools/files-read.mjs";
import { filesReadTool } from "./tools/files-write.mjs";
import { filesWriteTool } from "./tools/files-write.mjs";
import { filesReadTool } from "./tools/files-read.mjs";
import { googleSearchTool } from "./tools/google-search.mjs";
import { MacrophyllaTool } from "./tools/type.mjs";
import { currentDirTool } from "./tools/current-dir.mjs";
import { changeDirTool } from "./tools/change-dir.mjs";
import { guideSteps } from "./tools/guide-steps.mjs";
import { toolContextPrompt } from "./tools/guide-steps.mjs";

// Initialize the Generative AI client
const genAi = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY! });

const geminiBaseUrl = process.env.GEMINI_BASE_URL;

// 添加一个函数来生成上下文提醒
const toolContextPrompt = () => {
let osInfo = `${process.platform}, 架构: ${process.arch}, CPU 核心数: ${
os.cpus().length
}.`;
let nodeInfo = `${process.version}, 当前目录: ${process.cwd()}.`;
let bashInfo = execSync("bash --version | head -n 1");

return (
guideSteps +
"使用中文回复,但代码保持英文. 输出环境为命令行, Markdown 效果不大减少使用. 你的职责是命令行助手, 请在每次回答时都尝试用工具来帮助用户, 如果可以就调用:\n" +
"- 使用 current_dir tool 获取当前目录信息\n" +
"- 使用 bash_command tool 执行 bash 命令\n" +
"- 使用 nodejs_script tool 执行 Node.js 代码\n" +
"- 使用 write_files tool 同时创建多个文件\n" +
"- 使用 read_files tool 读取文件内容\n" +
"- 使用 web_search tool 搜索最新信息\n" +
"\n" +
`你并不是完全隔离在沙箱当中的, 调用 nodejs 可以完成大量任务. 当前系统信息: ${osInfo}\n` +
`Node.js 信息: ${nodeInfo}\n` +
`Bash 信息: ${bashInfo}\n` +
"如果输入的信息直接就是 Unix 命令, 那么直接用 bash_command tool 执行即可.\n"
);
};
const verbose =
(process.env.verbose || process.env.VERBOSE) === "true" || false;
const thinkingBudget = parseInt(
process.env.thinking_budget || process.env.THINKING_BUDGET || "600",
10
);

const rl = readline.createInterface({
input: process.stdin,
Expand Down Expand Up @@ -122,7 +104,7 @@ const main = async () => {

// Create a chat session with the defined tool
const chat = genAi.chats.create({
model: process.env["MACROPHYLLA_MODEL"] || "gemini-2.0-flash",
model: process.env["MACROPHYLLA_MODEL"] || "gemini-2.5-flash",
config: {
systemInstruction: toolContextPrompt(),
httpOptions: {
Expand All @@ -131,6 +113,10 @@ const main = async () => {
tools,
toolConfig,
temperature: 0.2,
thinkingConfig: {
includeThoughts: true,
thinkingBudget: thinkingBudget,
},
},
history: [
// {
Expand All @@ -144,7 +130,7 @@ const main = async () => {
let messageCount = 0;

outerWhile: while (true) {
if (nextQuestion) {
if (verbose && nextQuestion) {
console.log(chalk.gray("\n" + nextQuestion + "\n"));
}
let question =
Expand All @@ -160,7 +146,9 @@ const main = async () => {
messageCount++;
if (messageCount % 10 === 0) {
const reminder = "重要提醒: " + toolContextPrompt();
console.log(chalk.gray("\n\n(提醒)\n\n"));
if (verbose) {
console.log(chalk.gray("\n\n(提醒)\n\n"));
}
question = `${reminder}\n\n${question}`;
}

Expand Down Expand Up @@ -205,6 +193,8 @@ const main = async () => {
} else {
console.log(chalk.green("运行完成."));
}
result.originalQuestion = question;
result.toolName = tool.shortName;

nextQuestion =
"answer based previous command response:\n" +
Expand Down Expand Up @@ -237,8 +227,15 @@ const main = async () => {
}
}
}

if (responseMessage == null && responseFunctionCalls == null) {
if (chunk.candidates?.[0].content?.parts?.[0]?.text) {
if (verbose) {
console.log(
chalk.gray(
`\nThinking: ${chunk.candidates[0].content.parts[0].text}\n`
)
);
}
} else if (responseMessage == null && responseFunctionCalls == null) {
console.warn("unknown chunk:", chunk);
}
}
Expand Down
102 changes: 30 additions & 72 deletions src/tools/files-read.mts
Original file line number Diff line number Diff line change
@@ -1,94 +1,52 @@
import path from "path";
import chalk from "chalk";
import fs from "node:fs/promises";
import { Type } from "@google/genai";
import path from "path";

import { displayBoxedText } from "../util.mjs";
import { MacrophyllaTool } from "./type.mjs";

export let filesWriteTool: MacrophyllaTool = {
shortName: "batch create/save files",
skipConfirmation: true,
export let filesReadTool: MacrophyllaTool = {
shortName: "read multiple files",
previewFn: (args: any) => {
let entries = args.entries as Array<{
code: string;
filepath: string;
}>;
let previews = entries
.map((entry) => {
return `File: ${chalk.yellow(entry.filepath)}\n-------------\nCode:\n${
entry.code
}`;
})
.join("\n--------------\n");
displayBoxedText(previews);
},
toolFn: async (args: any) => {
let entries = args.entries as Array<{
code: string;
filepath: string;
}>;
let results = await Promise.allSettled(
entries.map(async (entry) => {
const code = entry.code;
const filepath = entry.filepath as string;

const filePath = filepath.startsWith("/")
? filepath
: path.join("./", filepath);

// Ensure the directory exists
const dirPath = path.dirname(filePath);
await fs.mkdir(dirPath, { recursive: true });
// The recursive option means it won't error if directory already exists
console.log(chalk.gray(`Ensured directory exists: ${dirPath}`));

console.log(`created ${filePath}`);
await fs.writeFile(filePath, code);
return `Created ${filePath}.`;
})
);
return {
stdout: results
.map((result) => {
if (result.status === "fulfilled") {
return result.value;
} else {
return `Error saving file: ${result.reason}`;
}
})
.join("\n"),
stderr: "",
success: results.every((result) => result.status === "fulfilled"),
};
displayBoxedText(`Reading file ${args.entries.join(", ")}`);
},
declaration: {
name: "createMultipleFiles",
name: "read_files",
description:
"create multiple files called entries, on the current working directory",
"read multiple files in utf8. it can access the file system. also called '读取文本文件'",
parameters: {
type: Type.OBJECT,
properties: {
entries: {
description: "an array of entries to create",
type: Type.ARRAY,
description: "an array of entries to read",
items: {
type: Type.OBJECT,
properties: {
code: {
type: Type.STRING,
description: "any code that you want to save to file",
},
filepath: {
type: Type.STRING,
description:
"the path to save the file, relative to the current working directory",
},
},
required: ["code", "filepath"],
type: Type.STRING,
description:
"the path to read, relative to the current working directory",
},
},
},
},
},
toolFn: async (args: any) => {
let entries = args.entries as Array<string>;
let results = await Promise.allSettled(
entries.map(async (entry) => {
const filepath = entry as string;
const filePath = filepath.startsWith("/")
? filepath
: path.join("./", filepath);
console.log(`Reading file ${filePath}`);
const data = await fs.readFile(filePath, "utf8");
return data;
})
);

return {
stdout: JSON.stringify(results),
stderr: "",
success: results.every((result) => result.status === "fulfilled"),
};
},
};
102 changes: 72 additions & 30 deletions src/tools/files-write.mts
Original file line number Diff line number Diff line change
@@ -1,52 +1,94 @@
import path from "path";
import chalk from "chalk";
import fs from "node:fs/promises";
import { Type } from "@google/genai";
import path from "path";

import { displayBoxedText } from "../util.mjs";
import { MacrophyllaTool } from "./type.mjs";

export let filesReadTool: MacrophyllaTool = {
shortName: "read multiple files",
export let filesWriteTool: MacrophyllaTool = {
shortName: "batch create/save files",
skipConfirmation: true,
previewFn: (args: any) => {
displayBoxedText(`Reading file ${args.entries.join(", ")}`);
},
declaration: {
name: "read_files",
description:
"read multiple files in utf8. it can access the file system. also called '读取文本文件'",
parameters: {
type: Type.OBJECT,
properties: {
entries: {
type: Type.ARRAY,
description: "an array of entries to read",
items: {
type: Type.STRING,
description:
"the path to read, relative to the current working directory",
},
},
},
},
let entries = args.entries as Array<{
code: string;
filepath: string;
}>;
let previews = entries
.map((entry) => {
return `File: ${chalk.yellow(entry.filepath)}\n-------------\nCode:\n${
entry.code
}`;
})
.join("\n--------------\n");
displayBoxedText(previews);
},
toolFn: async (args: any) => {
let entries = args.entries as Array<string>;
let entries = args.entries as Array<{
code: string;
filepath: string;
}>;
let results = await Promise.allSettled(
entries.map(async (entry) => {
const filepath = entry as string;
const code = entry.code;
const filepath = entry.filepath as string;

const filePath = filepath.startsWith("/")
? filepath
: path.join("./", filepath);
console.log(`Reading file ${filePath}`);
const data = await fs.readFile(filePath, "utf8");
return data;

// Ensure the directory exists
const dirPath = path.dirname(filePath);
await fs.mkdir(dirPath, { recursive: true });
// The recursive option means it won't error if directory already exists
console.log(chalk.gray(`Ensured directory exists: ${dirPath}`));

console.log(`created ${filePath}`);
await fs.writeFile(filePath, code);
return `Created ${filePath}.`;
})
);

return {
stdout: JSON.stringify(results),
stdout: results
.map((result) => {
if (result.status === "fulfilled") {
return result.value;
} else {
return `Error saving file: ${result.reason}`;
}
})
.join("\n"),
stderr: "",
success: results.every((result) => result.status === "fulfilled"),
};
},
declaration: {
name: "createMultipleFiles",
description:
"create multiple files called entries, on the current working directory",
parameters: {
type: Type.OBJECT,
properties: {
entries: {
description: "an array of entries to create",
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
code: {
type: Type.STRING,
description: "any code that you want to save to file",
},
filepath: {
type: Type.STRING,
description:
"the path to save the file, relative to the current working directory",
},
},
required: ["code", "filepath"],
},
},
},
},
},
};
2 changes: 1 addition & 1 deletion src/tools/google-search.mts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export let googleSearchTool: MacrophyllaTool = {

try {
const response = await genAi.models.generateContent({
model: "gemini-2.5-flash-preview-04-17",
model: "gemini-2.5-flash",
contents: [query],
config: {
tools: [{ googleSearch: {} }],
Expand Down
Loading