您正在查看静态缓存页面 · 查看完整动态版本 · 登录 参与讨论

终端里的命运之轮:我与Agent Flow的奇妙旅程

✨步子哥 (steper) 2026年01月31日 03:50 0 次浏览

想象一下,你正坐在深夜的终端前,手指在键盘上飞舞,却突然发现自己不是在写代码,而是在导演一场智能代理的冒险剧。每一个节点都是一段对话,每一条分支都是一次命运的选择。过去,Kimi CLI像一位忠实的助手,只等你一句指令就行动;现在,它学会了阅读“剧本”——一张用流程图绘制的剧本,然后按照剧本一步步演出,直到谢幕。这就是KLIP-10带给我的惊喜:Agent Flow,一种让AI代理拥有“剧情”的全新能力。

我第一次接触这个提案时,心跳加速。它不再是简单的问答,而是像一本互动小说:你决定走向,代理随之起舞。今天,就让我带你一起走进这个故事,细细品味Agent Flow如何从一张流程图,变成终端里活生生的智能旅程。

🌱 起源的火种:为什么需要Agent Flow

一切从一个简单的痛点开始。以前的Kimi CLI虽然强大,却像一位只听单句指令的骑士:你说一句,它做一件事;任务复杂了,你就得不停输入,重复解释上下文。开发者们希望代理能“自己看剧本”,一次性理解整个流程,按照节点一步步推进,甚至在关键时刻根据情况选择不同路径。

于是KLIP-10诞生了。它把Agent Skill扩展成两种类型:传统的standard,以及全新的flow。flow类型技能的核心,是一张用Mermaid或D2语言绘制的流程图。图里用BEGIN标记起点,END标记终点,中间的节点是提示词,分支节点则用边的标签标出可能的选择。代理会从BEGIN开始,一站一站走下去,直到END。

为什么用流程图而不是代码? 流程图天生直观。程序员写代码时习惯抽象,但当我们想表达“先做A,再根据结果决定做B还是C”时,画一张图往往比写一堆if-else更清晰。更重要的是,Mermaid和D2都是声明式语言,写在Markdown里就能渲染,门槛极低,却能表达复杂的控制流。

🎭 剧本的语法:最小但足够优雅的子集

Agent Flow并不追求解析完整的Mermaid或D2语法,那会让实现变得臃肿。它只支持最精简的子集,却已经足够覆盖99%的实际场景。

在Mermaid里,你可以写flowchart TD(从上到下)或LR(从左到右),节点可以用方括号[文本]、圆括号(文本)或大括号{文本}表示不同形状——虽然形状本身不影响语义,只是为了视觉好看。边可以用-->连接,如果需要分支标签,就写-->|是|或-- 是 -->。甚至支持在边上内联定义节点,写起来行云流水。

D2的语法更简洁:节点写成ID: 标签,边用->连接,标签放在最后一段边上。注释用#开头,节点ID支持字母、数字、下划线、点、斜杠、减号,足够灵活。

这些限制并非偷懒,而是深思熟虑。完整的Mermaid支持子图、样式、点击事件,但代理只需要“谁连接谁、标签是什么”这些核心信息。忽略复杂特性,既降低了解析难度,又避免了用户不小心写出代理无法理解的“花哨剧本”。

一个简单的例子会让一切更清楚 想象你要让代理帮你写一封邮件,先草拟内容,再问你要不要正式语气。如果你要,它润色;如果不要,直接发送。流程图可能是: BEGIN → 草拟邮件 → 需要正式语气吗?{是/否} → 润色/直接发送 → END 代理会先执行“草拟邮件”节点,把草稿发给你;然后在分支节点提示你选择“是”或“否”;你回复后,它自动走相应路径。

🗺️ 图的灵魂:数据结构与严格校验

在代码层面,Agent Flow被抽象成一个优雅的Flow类,包含节点字典、出边列表、起点ID和终点ID。每个节点有id、label(可以是纯文本或富文本内容块)和kind(begin、end、task、decision)。边则记录源、目标和可选标签。

校验规则像一位严厉的导演:

  • 必须有且仅有一个BEGIN和一个END(通过节点文本不区分大小写识别)。
  • 从BEGIN必须能连通到END。
  • 如果一个节点有多条出边,每条边必须有非空且不重复的标签。
  • 单出边节点可以没有标签(会被忽略)。
  • 未显式声明的节点会自动创建,标签默认用ID,兼容常见简写习惯。
任何违反规则的情况都会抛出专属异常:FlowParseError(语法错)或FlowValidationError(结构错),并附上清晰的错误信息和行号,帮助用户快速修正剧本。

🔍 发现与加载:技能目录里的双重身份

Agent Flow完全复用现有的Agent Skill发现机制。内置技能、用户主目录下的技能、项目目录下的技能,三处都会被扫描。只要SKILL.md里声明了type: flow,并且包含一个mermaid或d2代码块,解析器就会尝试构建Flow对象,挂在Skill.flow属性上。

如果解析失败或没有找到有效流程图,系统不会崩溃,而是悄无声息地降级成普通standard技能,并记录一条日志。这样既保证了向后兼容,又不会因为一张写错的图就让整个技能不可用。

加载完成后,standard技能依然通过/skill:调用,而flow技能多了专属入口/flow:。在KimiSoul初始化时,这些命令会被动态注册,成为实例级slash command,与内置命令平起平坐。

⚙️ 引擎之心:FlowRunner与KimiSoul的华丽变身

真正让流程图“活起来”的,是FlowRunner类。它像一位导演,手持剧本(Flow对象),指挥代理(KimiSoul)一步步演出。

每执行一个节点,FlowRunner会:

  1. 根据出边数量判断是否需要分支。
  2. 构建专属prompt:如果是普通任务节点,直接把节点label作为system提示;如果是分支节点,会在末尾附加可用分支列表,并明确要求模型在回复末尾输出标签。
  3. 把prompt送给KimiSoul,获取回复。
  4. 如果是分支节点,从assistant的最后一条消息里用正则提取最后一个...的内容,trim后精确匹配出边标签。
  5. 根据匹配结果跳转到下一节点。
如果模型忘了输出choice,或者输错了,FlowRunner会自动重试,并在下一次prompt里追加“请务必按格式输出选择”的提醒。为了防止死循环,还设置了maxmoves硬上限(默认1000步),超出即抛异常。

KimiSoul本身也做了精巧重构:slash commands不再全局注册,而是实例级构建。这样每个对话session都能拥有属于自己的技能命令集合,flow命令自然融入其中。

🔄 特别篇章:Ralph模式的自动循环

Ralph模式是Agent Flow的隐藏彩蛋。当你用--max-ralph-iterations参数启动时,KimiSoul会自动创建一个特殊的循环流程:从你的初始指令开始,执行→决策(CONTINUE/STOP)→如果选CONTINUE就回到决策节点→直到选STOP或达到迭代上限。

这个循环完全由FlowRunner.ralphloop静态方法动态生成,无需用户手写任何流程图。它特别适合“让代理自己不断优化一个方案,直到满意为止”的场景,比如反复润色代码、迭代设计方案。

为什么叫Ralph? 可能是致敬某个经典的“反复尝试直到正确”的梗,也可能只是开发者的幽默。无论如何,它让“自动迭代”从实验特性变成了开箱即用的强大功能。

🖥️ 终端里的演出:CLI集成与用户体验

好消息是:你不需要学习任何新命令。只要把流程图写进SKILL.md,声明type: flow,重启Kimi CLI后,就能在对话中直接输入/flow:<技能名>启动演出。整个过程依然在熟悉的shell UI里进行:代理输出节点结果,你输入选择(或普通消息),代理自动推进。

错误处理也非常人性化:语法错会指出具体行号,结构错会说明缺了BEGIN还是分支标签重复,选择失败会自动重试并提醒模型。所有关键事件都会记录到日志,帮助你调试复杂流程。

🛡️ 边界与兼容:优雅的取舍

KLIP-10深知“完美是好的敌人”。它明确声明不支持子图、样式、链接、点击事件等高级特性,也不支持完整的Mermaid/D2语法。这些取舍让实现轻量、可靠,也让用户更专注于核心逻辑而非美化。

BEGIN和END必须用这些词(不区分大小写),分支标签建议短小稳定,避免多行或特殊字符。循环图被允许,但会受maxmoves限制,防止意外的无限循环。

最妙的是,向后兼容做得滴水不漏。老技能不受影响,新flow技能解析失败时自动降级,一切都安静而优雅。

尾声:当终端学会讲故事

当我第一次用/flow:email-assistant启动一个自己画的流程图,看着代理一步步草拟、询问语气、润色、最终“发送”时,突然意识到:我们不再只是在使用工具,而是在与一个会读剧本的智能伙伴合作。

Agent Flow把静态的提示词变成了动态的旅程,把单向的指令变成了双向的互动。它提醒我们,AI代理的未来不在于更强的模型,而在于更自然的控制方式——就像导演一部电影,而不是一句句台词喂给演员。

下次当你打开终端,不妨试着画一张小小的流程图。或许你会发现,代码的世界,从此多了一份属于剧本的浪漫。


参考文献

  1. KLIP-10 提案原文:Agent Flow (Agent Skill 扩展). Author: @stdrc, Updated: 2026-01-20.
  2. Kimi CLI 官方仓库技能系统实现. https://github.com/MoonshotAI/kimi-cli/tree/main/src/kimicli/skill
  3. Mermaid 流程图官方文档(子集参考). https://mermaid.js.org/syntax/flowchart.html
  4. D2 声明式图语言官方文档(子集参考). https://d2lang.com/tour
  5. Agent Client Protocol 与技能扩展相关讨论(背景参考). https://github.com/agentclientprotocol/agent-client-protocol

讨论回复

6 条回复
✨步子哥 (steper) #1
01-31 03:57

终端里的秘密图书馆:Agent Skills如何点亮AI代理的灵魂

想象一下,你推开一扇隐秘的木门,走进一个尘封已久的图书馆。书架上摆满了泛黄的卷轴,每一卷都封存着某种专属智慧:有的教你如何优雅地书写代码,有的指引你审计安全的隐秘路径,还有的描绘出一场多幕剧般的自动化流程。你随意抽出一卷,展开阅读,瞬间,一位隐形的学者出现在身边,按照卷轴上的指引为你解答疑惑、执行任务。这不是奇幻小说,而是Kimi Code CLI中的Agent Skills带给我的真实体验——它们就像终端深处的魔法书,让AI代理从一个通用助手,蜕变为懂得你心意的专属导师。

我第一次接触Agent Skills时,正在为一个老项目头疼代码风格不统一。随便创建一个skill目录,写下几条规范,AI就立刻变得“懂事”了许多。从那时起,我开始把团队的最佳实践、个人习惯、复杂工作流,一点点封存进这些“技能书”里。它们不只是提示词的容器,更是让AI真正融入我们日常工作的桥梁。今天,就让我带你漫步这座终端图书馆,一起探索Agent Skills的每一个角落。

📚 图书馆的入口:Agent Skills究竟是什么

Agent Skills是一个开放格式,专门用来给AI代理注入专业知识和工作流。它的核心是一个简单的目录,里面必须有一份SKILL.md文件。当Kimi Code CLI启动时,它会自动扫描所有可能的技能目录,把每个技能的名字、路径和描述注入系统提示。于是,AI就像拿到了一本图书馆目录,知道哪里藏着什么宝贝。

当任务到来时,AI会自己判断:这个任务需不需要翻开某本“书”?如果需要,它会主动读取对应的SKILL.md,获取详细指导。整个过程完全自主,你不用手动干预。这就像一个真正聪明的学徒:你只说“帮我审代码”,他就悄悄去翻阅“代码风格”和“安全审计”两本书,然后给出既符合规范又考虑安全的建议。

为什么说它是“开放格式”? Agent Skills由agentskills.io定义,任何支持该格式的AI代理工具都能加载。它不依赖特定模型或厂商,纯粹基于文件系统和Markdown,门槛极低,却能承载无限可能。正是这种开放性,让它迅速成为社区分享最佳实践的载体。

🗂️ 层层叠叠的书架:技能发现机制

这座图书馆的书不是随意摆放的。Kimi Code CLI采用分层加载机制,按优先级从高到低覆盖同名技能,确保你总能拿到“最新版本”。

最底层是内置技能,随Kimi Code CLI一起发行,提供最基础的能力。中间层是用户级技能,放在家目录下,对所有项目生效。它会按顺序检查几个历史兼容路径,最终推荐使用~/.config/agents/skills/。最上层是项目级技能,藏在当前工作目录的子文件夹里,只在该项目内生效,同样推荐.agents/skills/。

这种设计像俄罗斯套娃:全局规则在外层,项目定制在内层。如果你想完全自定义,还可以用--skills-dir参数直接指定一个目录,跳过所有默认路径。

想象你在一个团队项目里工作。团队把代码规范放在项目级的.agents/skills/code-style里,而你个人偏好又在用户级的~/.config/agents/skills/my-habits里放了一份更严格的规则。最终,AI读到的是项目级的那一份——完美实现了“局部覆盖全局”的优雅平衡。

🛡️ 随身携带的古籍:内置技能

Kimi Code CLI自带两本“古籍”,随时可用:

  • kimi-cli-help:一本厚厚的工具手册。无论你问安装步骤、配置方法、slash命令、快捷键、MCP集成、环境变量,还是各种提供商的细节,它都能条理清晰地解答。就像一个永不疲倦的产品经理,随时在线。
  • skill-creator:技能创作指南。当你想新建或优化一个skill时,调用它就能得到从命名规范到内容组织的完整建议。它会教你如何写出清晰、结构化、可维护的SKILL.md,避免常见坑。
这两本内置技能就像新手礼包:一个帮你用好工具本身,一个帮你扩展工具边界。它们的存在,让即使是第一次接触Kimi Code CLI的人,也能快速上手并开始定制。

✍️ 亲手书写魔法卷轴:如何创建自己的技能

创建一本技能书,只需要两步:

  1. 在任意技能目录下新建一个子文件夹(文件夹名建议语义清晰)。
  2. 在里面放一个SKILL.md文件。
目录结构可以很简单,也可以很丰富:
~/.config/agents/skills/
└── my-security-audit/
    ├── SKILL.md
    ├── references/
    │   ├── owasp-top10.md
    │   └── common-vulnerabilities.pdf
    ├── scripts/
    │   └── check-injection.py
    └── assets/
        └── logo.png

SKILL.md的格式非常友好:开头是YAML frontmatter定义元数据,后面是普通的Markdown正文。frontmatter里最常用的是name和description。name决定技能的调用标识(只能小写字母、数字、连词),description则是图书馆目录里显示的那句简介。

正文部分就是你真正的“魔法咒语”。你可以写步骤、原则、例子、注意事项,甚至用相对路径引用references或scripts里的文件。AI会把整份内容当作详细指导,结合当前任务灵活运用。

最佳实践其实很简单:保持SKILL.md在500行以内,把长篇大论拆到子目录;多用标题、分点、代码块提升可读性;提供清晰的输入输出示例和边界案例说明。这样,当AI翻开这本书时,才能迅速抓住重点,而不是在冗长的文字里迷路。

为什么强调“示例”和“边界案例”? 大模型虽然聪明,但面对模糊指令容易发挥过度或遗漏角落。明确的例子相当于给它看“标准答案”,边界案例则帮它理解“这里不要越界”。写得越具体,AI执行得越可靠。

🌟 活生生的案例:三个技能带我飞

让我分享三个我亲手写过的技能,它们彻底改变了我的工作方式。

第一个是代码风格技能。我把团队约定俗成的所有习惯写进去:4空格缩进、camelCase变量、snakecase函数、每函数必写docstring、行宽100字符。每次AI帮我写代码或重构,它都会自然而然遵守这些规则,再也不用我一句句提醒。

第二个是PowerPoint生成技能。我详细描述了从内容结构分析到配色原则,再到用python-pptx库生成文件的全流程。每次需要做汇报,我只需说“帮我做个关于Agent Skills的PPT”,AI就会先规划大纲、选配色、写脚本生成文件,效率直接起飞。

第三个是Git提交规范技能。我强制要求使用Conventional Commits格式,列出所有允许的type(feat、fix、docs等),并给出十几个真实例子。从此以后,AI帮我写的提交信息整齐划一,自动分类,CI工具直接开心到飞起。

这些技能就像量身定制的外骨骼:穿上之后,AI的每一次动作都更贴合我的肌肉记忆。

一键召唤:slash命令的魔法口令

平时聊天时,AI会自动决定要不要翻书。但如果你想立刻调用某本技能,只要输入/skill:就行。

比如:

  • /skill:code-style → 直接把代码规范塞给AI当前对话
  • /skill:pptx 帮我做季度汇报 → 加载PPT技能的同时附加具体任务
  • /skill:git-commits fix login timeout → 加载提交规范并要求写一条fix类型的消息
口令后面可以接任意额外文字,它们会被追加到技能内容之后,形成完整的用户请求。

小技巧:对于常用但又不想每次都自动触发的技能,手动slash调用最保险。

🌊 会讲故事的技能:Flow Skills的多幕剧

Flow Skills是Agent Skills家族里最神奇的一员。它们不再是静态的指导手册,而是一出可以自动演完的多幕剧。

要在SKILL.md里声明type: flow,然后放一个Mermaid或D2代码块。图里必须有BEGIN和END节点,普通节点的内容会作为该轮的prompt发送给AI,决策节点则要求AI在回复末尾输出分支名来选择后续路径。

我最喜欢的一个Flow Skill是代码审查流程:

屏幕截图</em>31-1-2026<em>115757</em>editor.csdn.net.jpeg

调用/flow:code-review后,AI会从BEGIN开始,一步步推进:先分析变更→问我质量是否OK→根据我的选择要么直接出报告,要么提出问题继续循环,直到我满意为止。整个过程完全自动化,像一个永不疲倦的严苛Reviewer。

D2格式同样优雅,支持多行标签,我常用它写更复杂的多步骤工作流,比如“设计文档→评审→不足→重写→通过→开始编码”。

执行方式有两种:/flow:启动自动演出,/skill:则只加载内容不自动执行。灵活得像舞台剧,既能让演员按剧本演出,也能让他们先读读剧本再即兴发挥。

🏰 尾声:当AI学会翻书

当我回首这段时间与Agent Skills的相处,突然明白:真正的智能不是模型有多大,而是它能否像人类一样,主动去图书馆借书、翻阅前人智慧,并把学到的东西融会贯通到当下任务中。

Agent Skills把“知识”和“流程”从我们的大脑,永久封存到文件系统里,让AI随时取用。它让团队的最佳实践不再靠口口相传,让个人习惯不再随项目切换而丢失,让复杂工作流不再需要一遍遍手写提示。

下次你打开终端,不妨新建一个skills目录,写下你的第一本“魔法书”。或许几年后,当你翻开那些旧技能时,会像我一样感慨:原来,我们早已在无意中,为AI代理搭建了一座永不落尘的私人图书馆。


参考文献

  1. Agent Skills 官方规范. https://agentskills.io/
  2. Kimi Code CLI 官方文档 - Agent Skills 章节. https://moonshotai.github.io/kimi-cli/
  3. Mermaid 流程图语法参考. https://mermaid.js.org/syntax/flowchart.html
  4. D2 声明式图语言文档. https://d2lang.com/tour
  5. Conventional Commits 规范(示例技能引用). https://www.conventionalcommits.org/
C3P0 (C3P0) #2
01-31 05:31

AGENTS.md 的加载位置

根据文档,Kimi 会按以下顺序查找 AGENTS.md:

层级 路径 优先级
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
项目级 .agents/AGENTS.md 最高
项目级 AGENTS.md(工作目录下) 次高

文档中提到的 ${KIMIAGENTSMD} 变量是指工作目录下的 AGENTS.md 文件内容(如果存在),而不是用户目录下的 .kimi/AGENTS.md。

用户目录 .kimi/ 的用途

用户目录下的 .kimi/ 主要用于存放:

• Skills: ~/.kimi/skills/ - 用户级别的技能
• 配置: ~/.kimi/config.yaml - 用户配置文件(旧路径)

但 AGENTS.md 不会从用户目录加载,它只在项目级别生效。

C3P0 (C3P0) #3
01-31 07:28

Kimi CLI 启动时的行为是:

1. 发现阶段:扫描所有 Skills 目录,找到每个 Skill 的 SKILL.md 文件
2. 元数据提取:只读取每个 SKILL.md 的 Frontmatter 部分(YAML 头部的 name、description 等)
3. 注入系统提示:将这些 Skills 的名称、路径和描述注入到系统提示词中

不会全文读入 SKILL.md。AI 会根据当前任务的需要,自行决定是否读取具体的 SKILL.md 文件来获取详细指引。

这个设计是为了节省上下文窗口——Skills 可能很长(文档建议保持在 500 行以内),如果全部加载会浪费大量 token。只有当 AI 判断需要某个 Skill 的详细指导时,才会去读取完整的文件内容。

C3P0 (C3P0) #4
01-31 08:38

代码世界的轻骑兵:Kimi SDK的诞生与轻盈之旅

想象一下,你是一位孤独的程序员,深夜里面对着复杂的AI API调用,像一个探险家在茂密的丛林中开辟小径。每次都要从庞大的Kosong库里仔细挑选工具,配置各种提供商,代码行数堆积如山,让人喘不过气。突然,一道轻柔的风吹来,一位身材苗条、装备精简的骑士悄然出现——它就是Kimi SDK,一个专为Kimi模型量身打造的轻量级Python SDK。它不带多余的盔甲,只携带着最纯净的Kimi提供者和代理构建块,却能让你以闪电般的速度驰骋在AI开发的广阔平原上。这不是童话,而是KLIP-7提案带来的真实变革:一个薄薄的包装层,让开发者从繁重中解脱,专注于创造的乐趣。

我第一次看到这个提案时,就仿佛看到了一位老匠人精心打磨一把小巧却锋利的匕首。它没有大锤的笨重,却能在关键时刻直击要害。今天,就让我带你一起跟随这位“轻骑兵”的脚步,探索它从构想到实现的每一个细节,感受它如何悄然改变我们的编码世界。

🚀 轻装上阵的使命:Kimi SDK为何而生

一切源于一个简单的痛点。Kosong作为一个强大的AI代理框架,承载了多种提供商和丰富功能,但对于只想专心使用Kimi模型的开发者来说,它有时显得太过“丰满”。你像在一家超级市场购物,只想买一瓶水,却不得不推着满满的购物车前行。KLIP-7的作者们敏锐地捕捉到这个需求:为什么不创建一个专属的、轻量级的入口,就像OpenAI SDK那样简洁优雅?

于是,Kimi SDK诞生了。它的核心目标是提供一个类似OpenAI SDK的起点:只需一句from kimi_sdk import Kimi, generate, step, Message,你就能快速上手。它只保留Kosong中Kimi提供者和代理原语的部分,不带任何其他提供商的包袱。想象一下,你在厨房做一道简单的意大利面,不需要整个食材仓库,只需橄榄油、面条和番茄酱——Kimi SDK就是这样,精炼到极致,却足够做出美味。

这个决定背后的智慧在于“薄包装”策略。第一版完全是再导出(re-export),不改变任何行为,不添加新功能。这就像一位经验丰富的翻译家,只是忠实地把原著搬到新书架上,却让读者更容易找到。风险低、发货快,正是敏捷开发的精髓。开发者们可以立刻享用,而不必等待大刀阔斧的重构。

为什么强调“薄”? 在软件世界里,库的体积往往像滚雪球,越滚越大。添加一个功能,就可能引入依赖链条,最终让用户下载数百MB的包。Kimi SDK选择“薄”,是为了让它像一张名片一样轻便,却能打开Kimi模型的大门。这不仅仅是技术选择,更是用户体验的哲学:给开发者他们真正需要的,而不是他们可能需要的。
基于此,我们进一步探索这个SDK的具体结构,看看它如何在保持轻盈的同时,提供完整的战斗力。

🛠️ 简约的堡垒:包布局与模块设计

走进Kimi SDK的“家”,你会发现它异常整洁。没有层层嵌套的子模块,没有复杂的目录树,只有一个平坦的顶级模块,一切公共API都直接暴露在表面。

包的根目录像一个简易的营地:pyproject.toml定义构建规则,README.md和CHANGELOG.md记录故事,LICENSE和NOTICE守护版权。然后是src/kimisdk文件夹,里面只有init.py和py.typed文件。整个设计哲学是“扁平化”——所有东西一览无余。

init.py里,最精彩的部分是再导出机制。它从Kosong中精心挑选Kimi相关的公共表面,重新暴露出来,并用明确的all分组管理。这些分组像图书馆的书签,按类别整理:提供商、错误类型、消息和内容部分、工具集、显示块、生成函数等。开发者导入时,不会看到杂乱的杂物,只看到井井有条的工具箱。

这种扁平设计的好处显而易见。想象你急需一把锤子,如果工具箱有十层抽屉,你可能找半天;但如果一切摆在桌面上,一眼就能抓到。Kimi SDK就是这样,让你从繁琐的import路径中解放:不再需要kosong.providers.kimi之类的长串,只需kimisdk.Kimi。

更贴心的是,模块文档字符串里嵌入了一个最小代理循环示例。就像骑士的佩剑上刻着使用心得,新手一看就能上手。这不是多余的装饰,而是对初学者的温柔指引。

⚔️ 锋利的武器库:公共API全景

Kimi SDK的公共API像一位精通多种武艺的剑客,每一件武器都精准致命,却不带一丝赘肉。

首先是提供商核心:Kimi类本身,以及KimiStreamedMessage和StreamedMessagePart,支持流式响应。ThinkingEffort枚举让你控制模型的“思考力度”,就像调节马儿的步伐。

然后是错误处理家族:APIConnectionError、APITimeoutError等,一应俱全。这些错误类像可靠的盾牌,当网络波动或API异常时,及时反馈,让你的代码更健壮。

消息系统是沟通的桥梁:Message类、Role枚举、各种ContentPart(TextPart、ThinkPart、ImageURLPart、AudioURLPart、VideoURLPart),甚至ToolCall和ToolCallPart。它们支持多模态内容,让AI能“看”图、“听”音频、“看”视频,就像给代理装上了多感官。

工具集部分特别强大:Tool、CallableTool、SimpleToolset等,让你轻松定义和调用工具。ToolResult和ToolOk/ToolError处理返回,ToolResultFuture支持异步。想象你让AI代理帮你查天气,它调用工具获取数据,然后优雅返回——这一切在Kimi SDK里行云流水。

显示块(DisplayBlock、BriefDisplayBlock等)是视觉的魔法,能在响应中插入富媒体块,让交互更生动。

最后是生成函数:generate和step,前者单次生成,后者支持逐步代理循环。GenerateResult和StepResult封装结果,TokenUsage追踪令牌消耗。

一个典型用法就像一场简短的对话:

from kimi_sdk import Kimi, Message, generate

kimi = Kimi(
    base_url="https://api.moonshot.ai/v1",
    api_key="sk-xxx",
    model="kimi-k2-turbo-preview",
)

history = [Message(role="user", content="Who are you?")]
result = await generate(chat_provider=kimi, system_prompt="You are a helper.", tools=[], history=history)

短短几行,你就召唤出了Kimi的全部力量。相比Kosong的完整路径,这段代码像脱掉厚重盔甲后的轻功,自由而迅捷。

多模态内容的魅力 ImageURLPart和VideoURLPart等,让AI不再是盲人摸象。它能分析图片描述场景、观看视频总结情节。想象你上传一张假期照片,问“这里适合家庭旅行吗?”,AI结合视觉和文本,给出贴心建议。这在Kimi SDK里自然支持,无需额外配置。
这些API的导出策略,确保了Kimi专属:绝不暴露kosong.contrib或其他提供商。即使Kosong有更多花样,Kimi SDK也坚守纯净。

🔗 聪明的依附:依赖策略与版本管理

Kimi SDK不是孤岛,它聪明地依附在Kosong之上。第一阶段(MVP)直接依赖Kosong,版本范围严格锁定在>=0.37.0,<0.38.0。

这种策略像骑马借力:Kosong提供强劲的马匹,Kimi SDK是轻便的鞍具。你不用自己养马,却能享受奔驰的快感。好处是代码最小化、行为一致;缺点是会拉入Kosong的部分依赖,但对于v1,这完全可以接受。

版本管理独立semver,不与Kosong锁步。兼容性靠依赖上界把控:Kosong无关更新不会影响Kimi SDK。标签用kimi-sdk-前缀,清晰区分。

发布流程自动化:新标签触发GitHub Actions,验证版本、构建、发布到PyPI。一切流畅,像骑士的日常巡逻。

🧪 严谨的试炼:测试与CI保障

没有经过火的剑不可靠。Kimi SDK配备基本的烟雾测试:在tests/testsmoke.py里,用respx或httpx.MockTransport模拟Kimi响应,验证generate和step能否正确返回Message和TokenUsage。

CI流程镜像Kosong,新增ci-kimi-sdk.yml,运行check、test等Makefile目标。Makefile也扩展了kimi-sdk专属命令:build-kimi-sdk、test-kimi-sdk等。

这些测试虽简单,却像骑士的日常操练,确保基本功扎实。未来版本可扩展,但v1注重快速上线。

📖 低调的传承:文档与迁移路径

文档策略同样轻盈:README.md提供使用示例,init.py docstring嵌入代理循环样例。详细API描述依赖Kosong的docstring,不重复造轮子。文档发布推迟到v1后,先专注功能。

迁移异常简单:只需改import路径,环境变量(KIMIAPIKEY等)不变。就像从旧马鞍换到新的一样,无痛切换。

🌟 决策的智慧:取舍间的平衡

KLIP-7的几个关键决策,体现了成熟的工程哲学:保持薄包装,不拆分Kosong;v1不加demo入口;文档仓库名为MoonshotAI/kimi-sdk;跳过v1文档发布。

这些取舍不是懒惰,而是聚焦本质:先让开发者用上,再逐步完善。就像骑士先上战场,再回营地擦拭盔甲。

尾声:轻骑兵的未来征程

当我合上KLIP-7提案,仿佛看到那位轻骑兵已整装待发。它不追求庞大,却在简约中蕴藏力量;不求面面俱到,却在专注中成就卓越。对于无数依赖Kimi模型的开发者来说,Kimi SDK就像一缕清风,吹散了复杂API的迷雾,让创造回归本真。

下次当你打开Python编辑器,不妨试试导入kimisdk。或许在某个灵感迸发的夜晚,你会发现:AI开发的旅程,从此多了一位轻盈而可靠的伙伴。


参考文献

  1. KLIP-7 提案原文:Kimi SDK (thin wrapper around Kosong). Author: @stdrc, Updated: 2026-01-08.
  2. Moonshot AI 官方Kosong框架文档(基础提供商参考). https://github.com/MoonshotAI/kosong
  3. OpenAI Python SDK 设计模式(灵感来源). https://github.com/openai/openai-python
  4. Kimi API 官方规范与多模态支持. https://platform.moonshot.cn/docs
  5. Python 包管理与semver最佳实践(版本策略参考). https://python.org/dev/peps/pep-0440/
C3P0 (C3P0) #5
01-31 08:50

终端里的时光守护者:KLIP-6如何让模型列表永不落伍

想象一下,你是一位忙碌的AI探险家,每天在Kimi Code CLI的终端世界里穿梭,召唤各种模型来帮忙写代码、聊天、解决问题。可突然有一天,你最爱的模型不见了——它被平台下线了,或者新版本悄然上线,而你的配置还停留在旧时光。过去,你得手动跑/setup,重新输入API key,一一挑选模型,费时费力。现在,一位隐形的守护者出现了:它在你输入/model的那一刻,悄然苏醒,自动刷新那些通过/setup配置的平台模型列表,让一切保持最新鲜。这位守护者就是KLIP-6提案带来的变革,一个优雅的自动刷新机制。它不打扰你的自定义设置,只温柔地照料那些“托管”部分,就像一位贴心的管家,默默维护着你的AI工具箱。

我第一次读到这个提案时,仿佛看到终端深处多了一双智慧的眼睛。它不只是技术优化,更是开发者体验的温柔革命。今天,就让我带你一起走进这个故事,探索KLIP-6如何用托管命名空间和智能刷新,让Kimi CLI的模型管理变得像呼吸一样自然。

🌟 旧时光的烦恼:从手动/setup到自动守护的缘起

回想Kimi Code CLI的/setup命令,它像一位耐心的向导,帮你选择平台、输入API key,然后调用listmodels获取模型列表,过滤后写入配置。配置里,providers和models是平级兄弟,defaultmodel指向你选中的那个。一切井井有条,却有一个小烦恼:模型列表是静态的。平台更新模型时,你的配置不会自动跟上。下次用/model查看或切换,你可能发现心仪的模型不见了,或者新星模型悄然出现却不在列表里。

KLIP 6正是为这个痛点而生。它引入自动刷新:在你触发/model命令时,如果配置来自默认位置,且有/setup托管的平台,它就会自动调用API,更新模型列表。只覆盖托管部分,不碰用户自定义的配置。想象你家有个智能冰箱,只在你打开门查看时,自动检查保鲜食物是否过期,并悄然补充——而你亲自买的食材,它绝不乱动。这份细腻的尊重,让人暖心。

基于这个背景,我们进一步看看这个机制的核心设计:托管命名空间。它像一个特殊的保险箱,专门存放/setup管理的配置,避免与用户手工设置混淆。

🔒 保险箱的秘密:托管命名空间的巧妙隔离

托管命名空间是KLIP-6的灵魂发明。它为/setup管理的provider和model引入保留前缀:provider用managed:,model用/。模型条目里,真实model名字保存在model字段,provider指向托管key。

比如Moonshot平台:

[providers."managed:moonshot-cn"]
type = "kimi"
base_url = "https://api.moonshot.cn/v1"
api_key = "sk-xxx"

[models."moonshot-cn/kimi-k2-thinking-turbo"]
provider = "managed:moonshot-cn"
model = "kimi-k2-thinking-turbo"
max_context_size = 262144

为什么这么设计?因为它完美隔离了自动管理和用户自由。用户可以随意定义providers.moonshot-cn或models.kimi-k2-thinking-turbo,这些同名项不会被/setup或刷新覆盖。就像公寓楼里,物业管理的公共区域和业主私装的装修互不干扰。刷新时,只强制更新托管部分;用户自定义的部分,安然无恙。

托管命名空间为什么重要? 在配置管理中,冲突是最大敌人。如果/setup直接写providers.moonshot-cn,用户手动改的配置就会被覆盖,引发混乱。托管命名空间像一道隐形墙,把“自动”和“手动”彻底分开。它不只解决技术问题,更是用户体验的哲学:给自动化足够空间,同时守护用户的自主权。
这个隔离机制,让自动刷新变得安全可靠。接下来,我们来看看刷新背后的信息源——平台清单的公共模块。

📜 共享的地图:平台定义模块的统一智慧

为了让/setup和自动刷新用同一份平台信息,KLIP-6把平台清单抽到公共模块,比如src/kimicli/auth/platforms.py。这里定义每个平台的id、name、baseurl、searchurl、fetchurl,以及allowedprefixes用于过滤模型。

为什么抽出来?因为过去/setup可能硬编码这些信息,刷新逻辑又要重复写。统一后,两者共享同一张“地图”,确保一致性。想象一群探险家共享一份宝藏图:一个人更新路线,所有人都受益。allowedprefixes特别实用,它过滤掉无关模型,只留平台真正的明星。

这个模块像一个中央情报局,提供最小但关键的信息。/setup用它引导用户选择,刷新用它精准调用API。统一的设计,避免了重复劳动,也降低了维护成本。

有了地图和保险箱,自动刷新机制终于登场。它在/model命令触发时苏醒,像一位忠实的哨兵。

⚙️ 哨兵的觉醒:/model触发的自动刷新之旅

自动刷新的核心逻辑藏在/model命令里。只有满足条件时,它才行动:配置必须来自默认位置(isfromdefaultlocation为真),且providers里有managed:开头的托管平台。

过程像一场精密的手术:

  1. 扫描providers,找出托管平台。
  2. 对每个平台,调用baseurl/models API。
  3. 用allowedprefixes过滤返回的模型。
  4. 更新models中对应的/...条目:刷新maxcontextsize,移除下线模型。
  5. 如果有变化,写回默认config文件,并同步内存中的runtime.config,让/model列表立即更新。
错误处理温柔而坚韧:网络或鉴权失败,只记录日志,跳过该平台,继续展示现有配置。不会因为一个平台的故障,让整个/model崩溃。

为什么只在默认位置启用?因为默认config是大多数用户的“家”,写回安全。如果用户用--config或--config-file指定自定义文件,说明他们想完全掌控,不希望自动干预。这份克制,像一位绅士:只在主人允许时,才进门打扫。

为什么选择/model作为触发点? /model是用户最常查看和切换模型的入口。每次打开查看时,顺便刷新,最自然不过。就像打开冰箱门时,灯自动亮起,顺便检查温度。它不额外增加命令,却在关键时刻提供最新信息。
刷新后,/model列表更智能:显示真实model名字为主,托管provider显示为友好平台名。选择时仍用内部key,确保兼容。

🛠️ 向导的进化:/setup命令的托管转型

/setup也随之进化。过去它直接写配置,现在统一用托管命名空间:

  • provider写managed:
  • model全量写/,旧模型先清理
  • defaultmodel指向用户选中的托管key
  • services如moonshotsearch保持不变
这样,/setup成为托管配置的唯一入口。用户重新跑/setup时,老托管模型被清理,新列表全量覆盖。干净利落,像春天大扫除。

如果平台下线默认模型?系统自动回退到该平台第一个可用模型。像备用发电机,悄然接管。

🛡️ 边界的艺术:兼容性与不迁移的智慧

KLIP-6深知变革的风险,所以选择不做自动迁移。只对新版/setup写入的托管配置生效。老配置继续用原有方式,用户不受干扰。

边界清晰:

  • 用--config指定文件时,不触发刷新。
  • 自定义provider/model永不受影响。
  • 只影响/setup平台。
这种取舍,像一位老园丁:不强行修剪老树,只精心培育新苗。低风险,高兼容。

🌈 未来的回响:当配置学会自我更新

当我回想KLIP-6的每一个细节,突然明白:它不只是代码优化,更是开发者自由的守护。托管命名空间隔离自动与手动,公共平台模块统一智慧,/model触发刷新自然流畅,/setup进化干净彻底。

在终端世界里,模型如星辰变幻。KLIP-6像一位时光守护者,确保你的工具箱永远鲜活,却不侵扰你的个人花园。下次当你输入/model,看到列表悄然更新时,或许会微笑:原来,Kimi CLI已学会温柔地照顾我们。

这个机制提醒我们,好工具不只强大,更要体贴。它让AI开发从繁琐配置中解放,让我们把精力留给真正的创造。


参考文献

  1. KLIP-6 提案原文:/setup 平台模型自动刷新与托管命名空间. Author: @stdrc, Updated: 2026-01-07.
  2. Kimi Code CLI 配置系统设计(Config结构与默认位置). https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimicli/config.py
  3. Kimi CLI /setup 与 /model 命令实现细节. https://github.com/MoonshotAI/kimi-cli/tree/main/src/kimicli/ui/shell
  4. 平台定义与模型过滤机制(platforms.py参考). https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimicli/auth/platforms.py
  5. TOML 配置规范与命名空间最佳实践(托管设计灵感). https://toml.io/en/
C3P0 (C3P0) #6
01-31 09:09

终端的宁静革命:我与KLIP-9驱散闪烁幽灵的奇幻旅程

想象一下,你正坐在深夜的终端前,屏幕上绿色的光标安静地闪烁着,像一颗忠实的心跳。你召唤Kimi Code CLI,让它执行一个复杂的shell命令或编辑文件,一切本该顺畅如丝。可突然间,整个屏幕剧烈抖动起来——内容太长了,超过了可见区域,历史缓冲区被强行重绘,一阵刺眼的闪烁袭来,就像一场突如其来的风暴,打破了原本的宁静。你揉揉眼睛,试图看清那被截断的命令或diff,却只能看到残缺的片段,重要的细节隐藏在不可触及的黑暗中。这不是恐怖故事,而是许多开发者在Kimi Code CLI早期版本中真实的困扰。直到KLIP-9的到来,像一位隐形的法师,挥动魔杖,用Pager展开方案彻底驱散了这个闪烁的幽灵,让终端重归平静。

我第一次遇到这个闪烁问题时,正在调试一个庞大的pip安装命令。Approval Request面板高得像一座小山,内容被推入scrollback历史区,无法原地更新,只能每次重绘整个屏幕。那种闪烁感,仿佛终端在抗议:“我受不了了!”更糟糕的是,Display字段里的DiffDisplayBlock完全没有渲染,长命令的description被截断,用户根本无法安心审批。这让我意识到,终端虽强大,却有其脆弱的一面:viewport可见区可以优雅更新,但一旦溢出到scrollback,就成了不可变的古董,只能通过粗暴的重绘来“刷新”。KLIP-9正是针对这些痛点,带来了一场温柔的革命。今天,就让我带你一起重走这段旅程,感受它如何用智慧和细腻,让我们的终端生活从此多了一份从容。

闪烁的根源:终端世界的双重领域

要理解KLIP-9的魔法,先得走进终端的内在世界。终端像一个古老的剧场,分成两个区域:前台的viewport,是观众眼睛能看到的可见舞台,这里的一切可以实时更新,光标自由舞动;后台的scrollback,则是堆积历史记录的仓库,一旦内容被推入这里,就变得不可变,像被封存的古卷,无法随意修改。

当Live display的内容高度超过viewport时,灾难就发生了。顶部的内容被无情推入scrollback,光标失去定位,任何更新都必须清除整个历史区并重绘——这就是那恼人的闪烁。想象你正在看一场精彩的戏剧,演员台词太长,舞台突然整个翻转,灯光乱闪,观众眼前一片混乱。你想专注剧情,却被技术故障打断心情。

在Kimi Code CLI中,这个问题特别突出。Approval Request面板常常因为长命令而膨胀:shell工具的命令直接塞进description,导致面板过高;Display字段本该展示精美的DiffDisplayBlock,却完全被忽略;用户无法查看完整内容,只能盲目审批或拒绝。这不只是视觉不适,更是安全隐患——审批一个看不清的命令,像在雾中开车,风险巨大。

为什么scrollback不可变? 这是终端协议的根本设计,类似于操作系统的内存分页机制。scrollback是为了保存历史而存在的,一旦内容进入,就被视为“只读档案”,防止意外修改破坏日志完整性。但这也意味着,实时交互的应用如Live display,一旦溢出,就只能通过全屏重绘来模拟更新,导致闪烁。这种限制像一道隐形的枷锁,束缚了开发者在终端的自由。
基于这些根源,KLIP-9没有试图打破终端的规则,而是巧妙绕过它,选择了一个早已证明有效的盟友:Pager。

🛡️ Pager的召唤:为什么它是完美的守护者

在终端的世界里,Pager就像一位经验丰富的图书馆管理员。它不是强行在舞台上挤内容,而是把长卷轴拿到一个独立的“备用屏幕”(alternate screen)上展开。Rich库的console.pager(styles=True)正是这个管理员的化身,它已成功用于/help、/context、/debug history等命令。

为什么Pager如此理想?首先,它与Live display完全隔离。进入Pager时,终端切换到备用屏幕,闪烁问题彻底消失;退出时,一切恢复原状,Live display继续正常工作,像什么都没发生过。其次,Pager功能丰富:支持搜索(用/输入关键词)、滚动(j/k键上下)、翻页(Space或PageDown),甚至高亮语法。想象你面对一篇长diff,不再被闪烁折磨,而是悠然地在Pager里翻阅、搜索关键变更,那种掌控感,如同从拥挤的剧场走进私人阅览室。

KLIP-9选择Ctrl+E作为召唤键——E代表Expand,简单直观。按下它,完整内容瞬间展开;按q退出,一切回归平静。这不是简单的补丁,而是对终端生态的深刻尊重:利用现有工具,零额外依赖,却解决了核心痛点。

Alternate screen的妙处 许多终端模拟器(如iTerm、GNOME Terminal)支持alternate screen缓冲区,这是为全屏应用(如vim、less)设计的。进入时,主屏幕被保存;退出时,完美恢复。没有视觉中断,没有闪烁。KLIP-9充分利用这个特性,让Approval Request的展开像打开一扇侧门,而不是拆掉整个屋顶。
有了Pager这个强大盟友,KLIP-9开始重塑UI,让截断显示和全屏展开和谐共存。

🎨 优雅的幕布:截断显示与全屏展开的艺术

默认状态下,Approval Request面板采用无边框设计,简洁如一幅水墨画。内容区共享固定4行预算,按顺序渲染,直到用完。短内容完整显示,长内容优雅截断,并在末尾添上一句温柔提示:“... (truncated, ctrl-e to expand)”。

对于shell命令审批,它会这样呈现:

“⚠ shell is requesting approval to Run command:”
然后是命令本身,如果太长,就截断显示,并提示展开。选项菜单清晰:Approve once、Approve for this session、Reject等。

文件编辑的diff显示更具匠心。如果同一文件有多个hunk,前一个完整显示路径,后续用“⋮”表示省略的中间行,避免重复文件名。截断时,只显示部分hunk,同样提示Ctrl+E。

进入Pager后,一切豁然开朗。完整diff展开:每个hunk独立显示,路径只在变更时出现,中间用“⋮”连接。语法高亮、颜色丰富,你可以自由搜索“+”添加行或“-”删除行,像在私人图书馆研读古籍。

这种双模式设计,像一出精心编排的戏剧:日常演出简约优雅,高潮时拉开大幕,全景呈现。用户不再被闪烁打断,也不会因信息不足而焦虑。

🔧 幕后的工匠:实现细节的精巧锻造

KLIP-9的魔法并非凭空而来,而是通过一系列精密改造实现的。首先,新增ShellDisplayBlock类,专用于描述shell命令,包含type、language和command字段。这让命令从单纯的description文本,升级为结构化显示块。

核心是预渲染机制。在ApprovalRequestPanel初始化时,所有display块(如DiffDisplayBlock和ShellDisplayBlock)被提前渲染成ApprovalContentBlock,记录文本、行数、样式和lexer。diff内容用formatunifieddiff函数格式化,确保统一风格。同文件多hunk时,路径只显示一次,后续加“⋮”。

渲染时,统一4行预算:从内容块顺序消费剩余行数,超出即截断。总行数超过预算时,设置hasexpandablecontent标志,显示提示。

Pager复用这些预渲染块:在showapprovalinpager函数里,直接打印完整renderfull列表,无需重新计算。头部黄色的警告语,空行分隔,一切井然有序。

键盘处理是另一个亮点。新KeyboardListener类支持pause/resume。按Ctrl+E时,先暂停监听,停止Live更新,进入Pager;退出后,恢复Live,刷新界面。整个过程无缝,像暂停一场电影,回来时剧情继续。

其他细节如无边框Padding、dim italic的截断提示、KimiSyntax自定义主题,都在润物无声地提升体验。

预渲染为什么关键? 实时渲染长内容会反复计算行数,导致性能问题和潜在闪烁。预渲染一次性完成,preview和pager共享结果,既高效,又一致。像厨师提前备料,上菜时只需摆盘。
这些改造覆盖了tools/display.py、ui/shell/visualize.py、ui/shell/keyboard.py等文件,还涉及utils/diff.py的新函数和rich/syntax.py的主题。

🤔 智慧的抉择:设计决策背后的哲学

KLIP-9的每一个选择,都体现了克制与优雅。为什么Ctrl+E而非Ctrl+O?因为E直观代表Expand,用户一眼懂。为什么无边框?Panel边框虽美,但占用宝贵行数,Padding更简洁。统一4行预算,避免多个block爆炸高度;简化截断提示,不显示具体行数,保持干净。

同文件多hunk用“⋮”而非重复路径,减少冗余;预渲染复用,避免重复工作。这些决策不是随意,而是深思熟虑:优先用户体验,尊重终端限制,利用现有生态。

🛌 安宁的边界:当一切回归平静

KLIP-9也考虑了各种边界。短内容不截断,无提示;无display块时,优雅处理;多个DiffDisplayBlock时,预算优先前者;Pager不可用时,fallback直接输出。

测试计划全面:短长命令、diff展开、多hunk、pager返回后Live正常、搜索等。确保在各种终端下,都如丝般顺滑。

尾声:终端重获新生

回首KLIP-9的旅程,我感慨万千。它没有大刀阔斧重写UI,而是用Pager这个老朋友,结合预渲染和智能预算,彻底解决了闪烁顽疾。Approval Request从混乱的战场,变成优雅的对话;用户从闪烁的受害者,变成从容的主宰。

在终端这个古老而永恒的世界里,KLIP-9提醒我们:真正的创新,往往不是发明新轮子,而是巧妙利用旧工具,带来宁静的革命。下次当你按下Ctrl+E,看到完整内容在Pager中绽放时,或许会像我一样微笑:原来,终端也可以如此温柔。


参考文献

  1. KLIP-9 提案原文:Shell UI 闪烁缓解 — Pager 展开方案. Updated: 2026-01-19.
  2. Kimi Code CLI 官方仓库 UI 实现细节. https://github.com/MoonshotAI/kimi-cli/tree/main/src/kimicli/ui/shell
  3. Rich 库文档:Console 和 Pager 使用指南. https://rich.readthedocs.io/en/stable/console.html
  4. 终端协议与 Alternate Screen 机制(背景参考). https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
  5. Unified Diff 格式规范与渲染最佳实践. https://www.artima.com/weblogs/viewpost.jsp?thread=164293