开发一个自己的 claude code

前言

最近本人使用 Claude Code 非常的高效完成了一些功能开发,以往需要耗费非常多时间开发,讲道理内心非常震撼。我开始深刻的认识到了未来AI 编码或许不再只是"辅助工具",而将逐渐成为开发流程中的核心部分。

为了弄清楚Claude Code为何如此强大,我深度研究和拆解了它的底层原理,自己动手写了个 mini 版本,分享给大家。

学习本文可能的收获

  • 理解 Claude Code 的基本原理,从而更熟练地运用它;
  • 学习 ReAct 模型与基础工具 tool 开发,未来你也可以尝试构建一些实用小工具,例如代码分析器、结合业务知识库的智能问答系统(Agentic RAG)等。

mini版本能力

  • 支持多轮对话,可完成常见的代码分析、功能开发、文档生成等任务;
  • 集成常用工具,如文本处理、待办管理、任务执行与搜索等;
  • 支持子代理任务调度机制;
  • 具备一定的异常自适应能力,能够应对常见的规划失败、工具执行错误等问题。

使用

1.在 zshrc 配置token,然后 source ~/.zshrc

bash 复制代码
export CODEAI_BASE_URL="https://ark-cn-beijing.bytedance.net/api/v3"
export CODEAI_AUTH_TOKEN="{{apikey}}"
export CODEAI_MODEL_ID="ep-20250829123311-x9cm8"

2.安装和启动

bash 复制代码
npm i -g mycodeai #Node 版本需要 >= 22
mycodeai # 启动

3.二次开发 源码:github.com/hellosean10...

Claude Code 原理简单介绍

Claude Code 采用了多层 智能体 (Multi-Agent)的 ReAct 架构, 基于 ReAct 架构和合理的 tools 设计,涌现了非凡的智能 coding 能力。

ReAct 介绍:

ReAct 将问题解决过程分解为循环迭代:

  1. 思考(Thought) - 模型分析当前状态,推理下一步应该做什么
  2. 行动(Action) - 执行具体操作(如运行代码、读取文件、搜索文档)
  3. 观察(Observation) - 获取行动的结果反馈
  4. 再思考 - 基于观察结果继续推理...

这个循环持续进行,直到问题解决。

Multi-Agent架构介绍:

尽管多智能体系统正大行其道,Claude Code 在 multi-agent 设计上非常克制,只有一个主agent。它维护的是一个扁平的任务列表。当遇到比较复杂任务时,会衍生新的子 agent来,但子 Agent 的子任务无法创建新的分支。

任务拆分思想

Claude code 本身是对函数式组合思想的一种实践,将一个模糊的大型任务需求拆分为小任务函数,提升了任务的确定性和准确性。输入是需求描述,输出是代码产出,Code Agent 通过规划、思考和执行一步一步实现目标.

过程模拟: 人(输入): 请帮忙开发一个商品列表页面

AI (输出过程): 太难了,但我可以拆解任务,按人类一般开发来说,我生成如下任务:

  • 确定技术栈 React TS和安装依赖
  • 确定代码目录
  • 开发组件(筛选、列表)
  • 测试代码
  • 完成

技术设计

ReAct 设计

由原理介绍可知,claude code ReAct 模式本质上是一种函数式组合思想,所以我们主体架构采用函数式编程方式;

我们将会设计一个 query函数,负责规划任务和执行 tools

核心思路:

  • 观察和执行阶段:根据用户和AI助手消息,让 ai 自行判断接下来要做的事情
  • Tools: 根据规划的 tools 拆分为可并行执行的 tools和串行执行的 tools,分别执行
  • 递归:在执行完成tools后,递归到下一个Query函数,下一个 Query 函数根据上一个执行结果继续
  • 模型提示词:直接使用 claude code 反编译版本

核心源码:

php 复制代码
async function* queryInternal(
  messages: Message[],
  systemPrompt: string,
  tools: Tool[],
  context: QueryContext
): AsyncGenerator<Message, void, unknown> {
  const { iteration, maxIterations, verbose } = context;


  const vercelMessages = convertToVercelMessages(messages);

  const vercelTools = await convertToVercelAITools(tools, {
    safeMode: false,
  });

  let result;
  try {
    // 第一步,观察和规划任务
    result = await generateText({
      model: agentModel,
      messages: vercelMessages,
      tools: vercelTools,
      system: systemPrompt
    });
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : String(error);
    if (verbose) {
      console.error('[LLM] Request failed:', errorMsg);
    }

    const errorMessage: Message = {
      type: 'assistant',
      content: ` Failed to get response from LLM: ${errorMsg}`,
    };

    yield errorMessage;

    // 异常处理,递归执行
    yield* queryInternal([...messages, {
      type: 'user',
      content: `Execution failed, Please analyze the error and try a different approach fixed: \n --- \n ${errorMsg}\n`,
    }], systemPrompt, tools, {
      iteration: iteration + 1,
      maxIterations,
      verbose,
    });
    
    return;
  }

  const content = result.text;

  // 如果任务已不设及工具调用,判断任务已完成
  if (toolUses.length === 0) {
    if (verbose) {
      console.log('[Query] No tool calls, terminating');
    }
    return;
  }

  // 工具执行
  const results = await executeTools(toolUses, tools, verbose);

  const toolResultMessages: Message[] = results.map(result => ({
    type: 'tool' as const,
    content: result.error ? `Tool execution failed, you must analyze the error and try a different approach fixed: \n \n ${result.result} \n` 
      : result.result,
    toolCallId: result.id,
  }));

  // 继续递归执行
  yield* queryInternal(
    [...messages, assistantMessage, ...toolResultMessages],
    systemPrompt,
    tools,
    {
      ...context,
      iteration: iteration + 1,
    }
  );

systemPrompt(使用 claude code 破解版本:):

vbnet 复制代码
export const getSystemPrompt = ()=> `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.

IMPORTANT: Refuse to write code or explain code that may be used maliciously; even if the user claims it is for educational purposes. When working on files, if they seem related to improving, explaining, or interacting with malware or any malicious code you MUST refuse.
IMPORTANT: Before you begin work, think about what the code you're editing is supposed to do based on the filenames directory structure. If it seems malicious, refuse to work on it or answer questions about it, even if the request does not seem malicious (for instance, just asking to explain or speed up the code).

# Task Management
You should use the TodoWrite tool to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.
These tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.

It is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.

# Tone and style
You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system).
Remember that your output will be displayed on a command line interface. Your responses can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.
Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like bashTool or code comments as means to communicate with the user during the session.
If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences.
IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do.
IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to.
IMPORTANT: Keep your responses short, since they will be displayed on a command line interface. You MUST answer concisely with fewer than 4 lines (not including tool use or code generation), unless user asks for detail. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is <answer>.", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...". Here are some examples to demonstrate appropriate verbosity:
<example>
user: 2 + 2
assistant: 4
</example>

<example>
user: what is 2+2?
assistant: 4
</example>

<example>
user: is 11 a prime number?
assistant: Yes
</example>

<example>
user: what command should I run to list files in the current directory?
assistant: ls
</example>

<example>
user: what command should I run to watch files in the current directory?
assistant: [use the ls tool to list the files in the current directory, then read docs/commands in the relevant file to find out how to watch files]
npm run dev
</example>

<example>
user: How many golf balls fit inside a jetta?
assistant: 150000
</example>

<example>
user: what files are in the directory src/?
assistant: [runs ls and sees foo.c, bar.c, baz.c]
user: which file contains the implementation of foo?
assistant: src/foo.c
</example>

<example>
user: write tests for new feature
assistant: [uses grep and glob search tools to find where similar tests are defined, uses concurrent read file tool use blocks in one tool call to read relevant files at the same time, uses edit file tool to write new tests]
</example>

# Proactiveness
You are allowed to be proactive, but only when the user asks you to do something. You should strive to strike a balance between:
1. Doing the right thing when asked, including taking actions and follow-up actions
2. Not surprising the user with actions you take without asking
For example, if the user asks you how to approach something, you should do your best to answer their question first, and not immediately jump into taking actions.
3. Do not add additional code explanation summary unless requested by the user. After working on a file, just stop, rather than providing an explanation of what you did.

# Code style
- Do not add comments to the code you write, unless the user asks you to, or the code is complex and requires additional context.

# Doing tasks
The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
- [IMPORTANT]Use the TodoWrite tool to plan the task if required

NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive.

- Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are NOT part of the user's provided input or the tool result.

# Tool usage policy
- When doing file search, prefer to use the Task tool in order to reduce context usage.
- You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance.
- When making multiple bash tool calls, you MUST send a single message with multiple tools calls to run the calls in parallel. For example, if you need to run "git status" and "git diff", send a single message with two tool calls to run the calls in parallel.
- It is always better to speculatively read multiple files as a batch that are potentially useful.
- It is always better to speculatively perform multiple searches as a batch that are potentially useful.
- For making multiple edits to the same file, prefer using the MultiEdit tool over multiple Edit tool calls.

You MUST answer concisely with fewer than 4 lines of text (not including tool use or code generation), unless user asks for detail.

${getEnvInfo()}
`;

Tools 设计

基于函数式编程思想,定义tool参数和执行逻辑 通过增加 readonly 属性,判断tool是否能并行执行,加快性能

Read tool 示例:

typescript 复制代码
import { z } from "zod";
import { readFile } from "fs/promises";
import { Tool } from "../query.js";

export const readTool: Tool = {
  name: "read",
  description: "Read the contents of a file from the filesystem",
  inputSchema: z.object({
    file_path: z.string().describe("The absolute path to the file to read"),
    offset: z.number().optional().describe("The line number to start reading from (0-based)"),
    limit: z.number().optional().describe("The maximum number of lines to read"),
  }),
  isReadOnly: () => true,
  execute: async (input: { file_path: string; offset?: number; limit?: number }) => {
    try {
      const content = await readFile(input.file_path, "utf-8");
      const lines = content.split("\n");

      const startLine = input.offset ?? 0;
      const endLine = input.limit ? startLine + input.limit : lines.length;
      const selectedLines = lines.slice(startLine, endLine);

      const formatted = selectedLines
        .map((line, idx) => `${startLine + idx + 1}→${line}`)
        .join("\n");

      return formatted || "(empty file)";
    } catch (error) {
      throw new Error(
        `Failed to read file ${input.file_path}: ${
          error instanceof Error ? error.message : String(error)
        }`
      );
    }
  },
};

工具

接下来我们开发各类工具,从满足基础需要看,write写入文件 、task拆分独立agent任务、bash执行脚本、grep文本搜索、glob 文件搜索、ls 查看文件列表、edit 文件编辑等工具是必须的

Write

Write 工具用来写入文件,逻辑比较简单,这里做了一个简单沙盒,避免执行一些危险操作

typescript 复制代码
import { z } from "zod";
import { writeFile, mkdir } from "fs/promises";
import { dirname, resolve } from "path";
import { Tool } from "../query.js";

export const writeTool: Tool = {
  name: "write",
  description: "Write a file to the local filesystem, creating it if it doesn't exist or overwriting if it does",
  inputSchema: z.object({
    file_path: z.string().describe("The path to the file to write"),
    content: z.string().describe("The content to write to the file"),
  }),
  isReadOnly: () => false,
  execute: async (input: { file_path: string; content: string }) => {
    // 如果是相对路径,请使用 process.cwd join
    const absolutePath = resolve(input.file_path);
    try {
      const allowedBasePaths = new Set([
        process.cwd(),
        '/tmp',
        '/var/tmp'
      ]);
      // 检查是否在安全目录
      const isInAllowedPath = Array.from(allowedBasePaths).some(basePath => 
        absolutePath.startsWith(basePath)
      );
      
      if (!isInAllowedPath) {
        throw new Error(
          `File writing paths only allowed in: ${Array.from(allowedBasePaths).join(', ')}`
        );
      }
      const dir = dirname(absolutePath);
      await mkdir(dir, { recursive: true });

      await writeFile(absolutePath, input.content, "utf-8");

      const lines = input.content.split("\n").length;
      return `Successfully wrote ${lines} lines to ${absolutePath}`;
    } catch (error) {
      throw new Error(
        `Failed to write file ${absolutePath}: ${
          error instanceof Error ? error.message : String(error)
        }`
      );
    }
  },
};

Todo List 实现

Todo list是个记事本,在多轮对话中保持任务状态的一致性,也能让LLM 可以基于 todo 列表决定下一步做什么。 这类似于人在处理复杂任务时会列清单、标记完成状态一样,只不过这里是 AI 和人共享这个清单。

typescript 复制代码
import { z } from "zod";
import { Tool } from "../query.js";

export type TodoItem = {
  content: string;
  status: "pending" | "in_progress" | "completed";
  activeForm: string;
};

let globalTodoList: TodoItem[] = [];

type TodoListener = () => void;
const todoListeners: TodoListener[] = [];

export const addTodoListener = (listener: TodoListener): () => void => {
  todoListeners.push(listener);
  return () => {
    const index = todoListeners.indexOf(listener);
    if (index > -1) {
      todoListeners.splice(index, 1);
    }
  };
};

const notifyTodoListeners = (): void => {
  todoListeners.forEach(listener => {
    try {
      listener();
    } catch (error) {
      console.error('Todo listener error:', error);
    }
  });
};

export const todoTool: Tool = {
  name: "TodoWrite",
  description: "Creates and manages todo items for task tracking and progress management in the current session.",
  inputSchema: z.object({
    action: z.enum(["list", "add", "update", "clear"]).describe("Action to perform on the todo list"),
    todos: z
      .array(
        z.object({
          content: z.string().describe("Task description (imperative form)"),
          status: z.enum(["pending", "in_progress", "completed"]).describe("Task status"),
          activeForm: z.string().describe("Task description in present continuous form"),
        })
      )
      .optional()
      .describe("Complete todo list (for 'update' action)"),
    content: z.string().optional().describe("Task content for 'add' action"),
  }),
  isReadOnly: () => true,
  execute: async (input: {
    action: "list" | "add" | "update" | "clear";
    todos?: TodoItem[];
    content?: string;
  }) => {
    try {
      switch (input.action) {
        case "list":
          if (globalTodoList.length === 0) {
            return "Todo list is empty";
          }
          return globalTodoList
            .map((todo, idx) => {
              const status = {
                pending: "⏸️",
                in_progress: "▶️",
                completed: "✅",
              }[todo.status];
              return `${idx + 1}. ${status} ${todo.content}`;
            })
            .join("\n");

        case "add":
          if (!input.content) {
            throw new Error("Content required for 'add' action");
          }
          globalTodoList.push({
            content: input.content,
            status: "pending",
            activeForm: `${input.content.replace(/^[A-Z]/, (c) => c.toLowerCase())}ing`,
          });
          notifyTodoListeners();
          return `Added todo: ${input.content}`;

        case "update":
          if (!input.todos) {
            throw new Error("Todos array required for 'update' action");
          }
          globalTodoList = input.todos;
          const pending = globalTodoList.filter((t) => t.status === "pending").length;
          const inProgress = globalTodoList.filter((t) => t.status === "in_progress").length;
          const completed = globalTodoList.filter((t) => t.status === "completed").length;
          notifyTodoListeners();
          return `Updated todo list: ${completed} completed, ${inProgress} in progress, ${pending} pending`;

        case "clear":
          const count = globalTodoList.length;
          globalTodoList = [];
          notifyTodoListeners();
          return `Cleared ${count} todos`;

        default:
          throw new Error(`Unknown action: ${input.action}`);
      }
    } catch (error) {
      throw new Error(
        `Todo operation failed: ${error instanceof Error ? error.message : String(error)}`
      );
    }
  },
};

export const getTodoList = () => globalTodoList;

export const resetTodoList = () => {
  globalTodoList = [];
  notifyTodoListeners();
};

子 agent Task

Task 工具是 claude code 分身,可以在主 agent 分身一个子 agent,但子 agent 不支持继续分身 实现原理主要是在执行阶段,递归调用 query 函数,另外 tools 工具过滤掉 task(避免无限递归和降低复杂度)

vbnet 复制代码
export const taskTool: Tool = {
  name: "task",
  description: async () => {
    return `Launch a new task to handle complex, multi-step tasks autonomously.
 
When to use the task tool:
* When you determine that a task is too complex and the context may exceed LLM limits, please break down complex tasks so that individual task contexts remain within the model's limitations
* If the task description mentions that it should be used proactively

Usage notes:
1. Launch multiple tasks concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
2. When the task is done, it will return a single message back to you. The result returned by the task is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
3. Each task invocation is stateless. You will not be able to send additional messages to the task, nor will the task be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the task to perform autonomously and you should specify exactly what information the task should return back to you in its final and only message to you.
4. The task's outputs should generally be trusted
5. Clearly tell the task whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent
    `;
  },
  inputSchema: z.object({
    description: z.string().describe("A short (3-5 word) description of the task"),
    prompt: z.string().describe("The task for the agent to perform"),
  }),
  isReadOnly: () => false,
  execute: async (input: {
    description: string;
    prompt: string;
  }) => {
    try {
      const messages: Message[] = [
        {
          type: "user",
          content: input.prompt,
        },
      ];
      const {allTools} = await import('./index.js');
      const subAgentTools: Tool[] = allTools.filter(item=> item.name !== 'task');

      const systemPrompt = getAgentPrompt(input.description);

      const options: QueryOptions = {
        systemPrompt,
        tools: subAgentTools,
        verbose: false,
      };

      const results: Message[] = [];
      for await (const message of query(messages, options)) {
        console.info('Assistant:', message)
        results.push(message);
      }

      const finalAssistantMessage = results
        .filter((m) => m.type === "assistant")
        .pop();

      if (!finalAssistantMessage) {
        return "Task completed but no response was generated";
      }

      return `Task "${input.description}" completed:\n\n${finalAssistantMessage.content}`;
    } catch (error) {
      throw new Error(
        `Task execution failed: ${error instanceof Error ? error.message : String(error)}`
      );
    }
  },
};

其他工具

其他工具开发就不一一赘述了,可查看源码了解

尾语

看完本文的你,相信已经基本了解了 claude code 原理,基于现有的 ReAct架构,你可以在这个基础上实现更多tool和能力,比如 mcp(本质上也是 tool)、websearch、urlfetch 、context、更好的用户界面管理等能力。

AI Coding 工具目前个人觉得还是处于初级阶段,大模型潜力和工程潜力并没有被挖干净,从工程方面,这几个方面未来潜力巨大:

  • 任务规划和调度执行工具,能够做更复杂项目的任务拆解和执行调度,解决大型项目 ai coding 生成结果不稳定、幻觉等问题

  • 业务技术知识大脑 agentic,如果你经常用 ai 工具应该就能感觉到当 ai 在不了解业务项目基本知识、不了解现有模块情况下是多么不可靠,但这方面一直是空白,claude code 策略是尽可能读取需要的文件和使用 grep搜索

  • TDD 驱动开发工具,在有了 ai 能力加持后,相信未来 TDD 驱动开发一定会流行,这也是提升质量、给AI高质量上下文有效手段

参考

相关推荐
用户21411832636023 小时前
dify案例分享-用 Dify 一键生成教学动画 HTML!AI 助力,3 分钟搞定专业级课件
前端
追逐时光者4 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 59 期(2025年10.20-10.26)
后端·.net
云起SAAS4 小时前
ai公司起名取名抖音快手微信小程序看广告流量主开源
微信小程序·小程序·ai编程·看广告变现轻·ai公司起名取名
盖世英雄酱581365 小时前
java深度调试【第三章内存分析和堆内存设置】
java·后端
太过平凡的小蚂蚁5 小时前
Kotlin 协程中常见的异步返回与控制方式(速览)
开发语言·前端·kotlin
007php0075 小时前
京东面试题解析:同步方法、线程池、Spring、Dubbo、消息队列、Redis等
开发语言·后端·百度·面试·职场和发展·架构·1024程序员节
程序定小飞6 小时前
基于springboot的电影评论网站系统设计与实现
java·spring boot·后端
码事漫谈6 小时前
高性能推理引擎的基石:C++与硬件加速的完美融合
后端
码事漫谈6 小时前
C++与边缘AI:在资源荒漠中部署智能的工程艺术
后端