citrus-code 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. citrus_code-0.1.0/LICENSE +22 -0
  2. citrus_code-0.1.0/PKG-INFO +412 -0
  3. citrus_code-0.1.0/README.md +390 -0
  4. citrus_code-0.1.0/pyproject.toml +38 -0
  5. citrus_code-0.1.0/src/citrus_code/__init__.py +0 -0
  6. citrus_code-0.1.0/src/citrus_code/__main__.py +5 -0
  7. citrus_code-0.1.0/src/citrus_code/agent/__init__.py +0 -0
  8. citrus_code-0.1.0/src/citrus_code/agent/agent.py +255 -0
  9. citrus_code-0.1.0/src/citrus_code/agent/cancellation.py +66 -0
  10. citrus_code-0.1.0/src/citrus_code/agent/context.py +20 -0
  11. citrus_code-0.1.0/src/citrus_code/agent/events.py +97 -0
  12. citrus_code-0.1.0/src/citrus_code/agent/hooks.py +37 -0
  13. citrus_code-0.1.0/src/citrus_code/agent/loop.py +429 -0
  14. citrus_code-0.1.0/src/citrus_code/agent/message_queue.py +44 -0
  15. citrus_code-0.1.0/src/citrus_code/agent/state.py +13 -0
  16. citrus_code-0.1.0/src/citrus_code/agent/tool_runner.py +161 -0
  17. citrus_code-0.1.0/src/citrus_code/agent/types.py +34 -0
  18. citrus_code-0.1.0/src/citrus_code/ai/__init__.py +0 -0
  19. citrus_code-0.1.0/src/citrus_code/ai/completion.py +30 -0
  20. citrus_code-0.1.0/src/citrus_code/ai/events.py +78 -0
  21. citrus_code-0.1.0/src/citrus_code/ai/provider.py +20 -0
  22. citrus_code-0.1.0/src/citrus_code/ai/providers/__init__.py +0 -0
  23. citrus_code-0.1.0/src/citrus_code/ai/providers/chat_completions.py +503 -0
  24. citrus_code-0.1.0/src/citrus_code/ai/providers/chat_stream_types.py +47 -0
  25. citrus_code-0.1.0/src/citrus_code/ai/providers/deepseek.py +104 -0
  26. citrus_code-0.1.0/src/citrus_code/ai/providers/fake.py +101 -0
  27. citrus_code-0.1.0/src/citrus_code/ai/providers/tool_call_buffer.py +73 -0
  28. citrus_code-0.1.0/src/citrus_code/ai/registry.py +40 -0
  29. citrus_code-0.1.0/src/citrus_code/ai/types.py +142 -0
  30. citrus_code-0.1.0/src/citrus_code/cli/__init__.py +5 -0
  31. citrus_code-0.1.0/src/citrus_code/cli/main.py +258 -0
  32. citrus_code-0.1.0/src/citrus_code/cli/preview.py +55 -0
  33. citrus_code-0.1.0/src/citrus_code/cli/prompts.py +52 -0
  34. citrus_code-0.1.0/src/citrus_code/cli/render.py +306 -0
  35. citrus_code-0.1.0/src/citrus_code/cli/session_files.py +75 -0
  36. citrus_code-0.1.0/src/citrus_code/cli/terminal.py +280 -0
  37. citrus_code-0.1.0/src/citrus_code/cli/theme.py +51 -0
  38. citrus_code-0.1.0/src/citrus_code/coding/__init__.py +0 -0
  39. citrus_code-0.1.0/src/citrus_code/coding/coding_session.py +287 -0
  40. citrus_code-0.1.0/src/citrus_code/coding/compaction/__init__.py +5 -0
  41. citrus_code-0.1.0/src/citrus_code/coding/compaction/controller.py +168 -0
  42. citrus_code-0.1.0/src/citrus_code/coding/compaction/summarize.py +125 -0
  43. citrus_code-0.1.0/src/citrus_code/coding/compaction/tokens.py +48 -0
  44. citrus_code-0.1.0/src/citrus_code/coding/context_hooks.py +41 -0
  45. citrus_code-0.1.0/src/citrus_code/coding/history.py +47 -0
  46. citrus_code-0.1.0/src/citrus_code/coding/project_context.py +44 -0
  47. citrus_code-0.1.0/src/citrus_code/coding/session/__init__.py +30 -0
  48. citrus_code-0.1.0/src/citrus_code/coding/session/manager.py +200 -0
  49. citrus_code-0.1.0/src/citrus_code/coding/session/projection.py +50 -0
  50. citrus_code-0.1.0/src/citrus_code/coding/session/recovery.py +33 -0
  51. citrus_code-0.1.0/src/citrus_code/coding/session/storage.py +138 -0
  52. citrus_code-0.1.0/src/citrus_code/coding/session/types.py +108 -0
  53. citrus_code-0.1.0/src/citrus_code/coding/tools/__init__.py +35 -0
  54. citrus_code-0.1.0/src/citrus_code/coding/tools/bash.py +5 -0
  55. citrus_code-0.1.0/src/citrus_code/coding/tools/edit.py +101 -0
  56. citrus_code-0.1.0/src/citrus_code/coding/tools/edit_text.py +172 -0
  57. citrus_code-0.1.0/src/citrus_code/coding/tools/factory.py +57 -0
  58. citrus_code-0.1.0/src/citrus_code/coding/tools/find.py +86 -0
  59. citrus_code-0.1.0/src/citrus_code/coding/tools/grep.py +151 -0
  60. citrus_code-0.1.0/src/citrus_code/coding/tools/ls.py +61 -0
  61. citrus_code-0.1.0/src/citrus_code/coding/tools/mutation.py +39 -0
  62. citrus_code-0.1.0/src/citrus_code/coding/tools/paths.py +11 -0
  63. citrus_code-0.1.0/src/citrus_code/coding/tools/powershell.py +5 -0
  64. citrus_code-0.1.0/src/citrus_code/coding/tools/processes.py +103 -0
  65. citrus_code-0.1.0/src/citrus_code/coding/tools/read.py +133 -0
  66. citrus_code-0.1.0/src/citrus_code/coding/tools/shell.py +218 -0
  67. citrus_code-0.1.0/src/citrus_code/coding/tools/truncate.py +87 -0
  68. citrus_code-0.1.0/src/citrus_code/coding/tools/write.py +94 -0
  69. citrus_code-0.1.0/src/citrus_code/config.py +34 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Citrus Code contributors
4
+ Copyright (c) 2025 Mario Zechner (pi)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,412 @@
1
+ Metadata-Version: 2.4
2
+ Name: citrus-code
3
+ Version: 0.1.0
4
+ Summary: A small Python coding agent inspired by pi, built as a learning project.
5
+ Keywords: coding-agent,agent,cli,learning,deepseek
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: httpx>=0.28.1
14
+ Requires-Dist: prompt-toolkit==3.0.53
15
+ Requires-Dist: pydantic>=2,<3
16
+ Requires-Dist: rich==14.2.0
17
+ Requires-Python: >=3.12
18
+ Project-URL: Repository, https://github.com/big-orange947/citrus-code
19
+ Project-URL: Issues, https://github.com/big-orange947/citrus-code/issues
20
+ Project-URL: Documentation, https://github.com/big-orange947/citrus-code#readme
21
+ Description-Content-Type: text/markdown
22
+
23
+ # Citrus Code
24
+
25
+ **参考 pi 的核心设计,带初学者用 Python 一步步实现一个精简的 coding agent。**
26
+
27
+ | 用途 | 名称 |
28
+ | --- | --- |
29
+ | 项目名称 | Citrus Code |
30
+ | GitHub 仓库 | [big-orange947/citrus-code](https://github.com/big-orange947/citrus-code) |
31
+ | Python 安装包 | `citrus-code` |
32
+ | 终端命令 | `citrus` |
33
+ | Python 模块 | `citrus_code` |
34
+ | 默认会话目录 | `<工作目录>/.citrus-code/sessions/` |
35
+
36
+ 这是一个持续实现中的学习项目,参考 [pi](https://github.com/earendil-works/pi) 的核心设计思路,用 Python 理解模型调用、流式事件、工具执行和 Agent 运行时如何协作。
37
+
38
+ 适合有 Java 或其他语言基础、正在学习 Python,同时对 Agent 开发有一些了解的读者。项目不使用 LangChain、LangGraph 等 Agent 框架;网络通信和数据校验分别使用 HTTPX 与 Pydantic,其余流程逐步实现。
39
+
40
+ > 当前已实现模型调用层、ReAct 主循环、Agent 状态、四个 Hooks、参考 pi 的八个内置工具、会话保存与恢复、带 Markdown 的 CLI、AGENTS.md 加载、本地图片读取,以及按 1M 窗口、60% 阈值实现的自动上下文压缩。基础 CLI 已手动运行;新版界面、图片接入和压缩流程仍需进一步交互与真实模型验证。
41
+
42
+ ## 学习目标与设计思路
43
+
44
+ - **模型接入与 Agent 运行时分开。** Provider 处理具体服务的协议,运行时使用统一的消息和事件。
45
+ - **以简单的工具调用循环为核心。** 模型提出动作,程序执行工具,把结果放回上下文,再让模型继续判断。
46
+ - **流式生成不等于工具执行。** 工具参数可以分段到达;完整接收并解析后,才能进入执行阶段。
47
+ - **工具声明和实际校验使用同一个来源。** Pydantic 参数模型既生成 JSON Schema,也校验工具收到的实际参数。
48
+ - **逐步补齐工程能力。** 在核心流程清楚的基础上,再加入状态管理、Hooks、CLI、会话保存和上下文管理。
49
+
50
+ 本项目是学习实现,并非 pi 的官方 Python 移植版,也不承诺与其接口和功能完全一致。
51
+
52
+ ## 当前进度
53
+
54
+ | 模块 | 当前能力 |
55
+ | --- | --- |
56
+ | 消息类型 | 系统、用户、助手、工具结果消息;文字块、图片块与工具调用块 |
57
+ | Provider 接口 | 统一模型信息和异步事件流,提供注册与查找 |
58
+ | FakeProvider | 使用预设文字模拟流式响应,便于理解异步生成器 |
59
+ | DeepSeek 接入 | HTTP 请求、SSE 读取、非思考模式、Flash 图片输入、用量统计和错误事件 |
60
+ | 工具调用解析 | 按索引拼接多个工具调用的参数,转换成统一工具事件 |
61
+ | 生成截断处理 | 默认输出上限 32,768 tokens;回复被截断时不执行该回复中的工具调用 |
62
+ | 完整回答接口 | `complete()` 消费事件流并返回最终助手消息 |
63
+ | 工具接口 | 区分模型可见的工具定义与 Python 执行实现 |
64
+ | Agent 运行时 | 模型与工具循环、连续对话状态、流式事件、可选轮数限制 |
65
+ | Hooks | 模型调用前后、工具执行前后;支持拦截工具与处理结果 |
66
+ | coding 工具 | read、write、edit、bash、powershell、grep、find、ls |
67
+ | 输出控制 | read/命令输出按 2000 行或 50KB 截断;搜索工具提供数量和字节限制 |
68
+ | 工具运行器 | 按名称查找工具,将执行结果或异常转换成工具结果消息 |
69
+ | 会话存储 | JSONL 追加保存、加载校验、消息树与分支选择、当前分支上下文重建 |
70
+ | CodingSession | 按事件保存消息、安全切换分支、取消与排队消息入口 |
71
+ | CLI | 交互与单次任务、流式输出、运行中追加消息、会话恢复、末尾缺失工具结果修复 |
72
+ | 终端展示 | 原创字符 π 标识、欢迎面板、多行输入、状态栏、Markdown 流式预览、代码高亮和工具结果卡片 |
73
+ | 项目指令 | 启动时读取工作目录下的 AGENTS.md,通过模型前 Hook 组装当前请求 |
74
+ | 上下文压缩 | 估计达到 600,000 tokens 时生成摘要,保留当前要求及近期完整交互,保存压缩记录 |
75
+
76
+ 工具名称、主要参数和默认组合参考 pi。实现细节、系统程序依赖、图片能力和取消处理的边界见 [工具说明](https://github.com/big-orange947/citrus-code/blob/main/docs/tools.md)。
77
+
78
+ 会话存储的接口、文件格式和恢复边界见 [会话说明](https://github.com/big-orange947/citrus-code/blob/main/docs/sessions.md)。带自动保存的任务通过 `CodingSession.prompt()` 运行。命令入口见 [CLI 使用说明](https://github.com/big-orange947/citrus-code/blob/main/docs/cli.md)。
79
+
80
+ 自动压缩策略与失败边界见 [上下文压缩说明](https://github.com/big-orange947/citrus-code/blob/main/docs/compaction.md)。当前固定假设模型支持 1,000,000 tokens;估计达到 60% 时压缩。摘要调用会消耗模型额度。
81
+
82
+ ## 快速开始
83
+
84
+ 当前版本从源码安装,需要 Python 3.12 或更新版本,以及 [uv](https://docs.astral.sh/uv/)。
85
+ 先克隆仓库并安装锁定的依赖:
86
+
87
+ ```powershell
88
+ git clone https://github.com/big-orange947/citrus-code.git
89
+ cd citrus-code
90
+ uv sync --locked
91
+
92
+ # 先体验终端界面,不调用模型,也不需要 API Key
93
+ uv run citrus --fake
94
+ ```
95
+
96
+ 使用真实模型时,在 PowerShell 中配置自己的密钥,再指定要处理的项目:
97
+
98
+ ```powershell
99
+ $env:DEEPSEEK_API_KEY = "替换为你自己的 API Key"
100
+ uv run citrus --cwd "D:\project\your-project"
101
+ ```
102
+
103
+ 进入交互模式后直接描述任务,例如“读取项目说明,介绍代码结构”。在工作目录中有
104
+ `AGENTS.md` 时,启动时会加载其中的项目约定。模型调用会消耗自己的 API 额度,
105
+ 工具会在所选工作目录中执行任务。
106
+
107
+ ## 安装发行版
108
+
109
+ 需要 Python 3.12 或更新版本。以下命令安装已经发布到 PyPI 的版本;首次发布前请使用上方的源码安装方式。安装包名为 `citrus-code`,启动命令为 `citrus`。
110
+
111
+ 使用 uv 安装:
112
+
113
+ ```powershell
114
+ uv tool install --python 3.12 citrus-code
115
+ ```
116
+
117
+ 没有 uv 时,可以使用已安装的 pipx,或通过 pip 安装到当前 Python 环境:
118
+
119
+ ```powershell
120
+ pipx install citrus-code
121
+
122
+ # 或安装到当前 Python 环境
123
+ python -m pip install citrus-code
124
+ ```
125
+
126
+ 在 PowerShell 中配置自己的模型密钥并启动:
127
+
128
+ ```powershell
129
+ $env:DEEPSEEK_API_KEY = "替换为你自己的 API Key"
130
+ citrus --cwd "D:\project\your-project"
131
+ ```
132
+
133
+ pipx 和 uv tool 为命令行应用建立独立环境。需要编辑源码时,使用下方的开发安装方式。
134
+
135
+ ## 常用命令
136
+
137
+ 在仓库根目录运行:
138
+
139
+ ```powershell
140
+ uv sync --locked
141
+
142
+ # 离线交互,使用预设文字,不需要 API Key
143
+ uv run citrus --fake
144
+
145
+ # 设置下文的 DeepSeek 环境变量后,进入真实模型交互
146
+ uv run citrus
147
+
148
+ # 单次任务
149
+ uv run citrus "读取 pyproject.toml,说明项目依赖"
150
+
151
+ # 列出保存的会话;恢复最近会话
152
+ uv run citrus --sessions
153
+ uv run citrus --resume
154
+ ```
155
+
156
+ 默认会话保存在工作目录的 `.citrus-code/sessions/`。交互时使用 `/help` 查看命令;运行期间可继续输入引导消息,Ctrl+C 请求取消。Windows 默认提供 PowerShell,可用 `--shell bash` 为新会话选择 Git Bash。离线模型只生成固定文字,不能完成编程任务。
157
+
158
+ 输入框中 Enter 发送,Alt+Enter 或 Ctrl+J 换行,Tab 补全命令。助手回答支持 Markdown;较长的工具结果默认只展示预览,使用 `--verbose` 启动可查看完整工具返回。升级依赖前先退出正在运行的旧 CLI,避免 Windows 锁住启动程序。
159
+
160
+ | 操作 | 命令或按键 |
161
+ | --- | --- |
162
+ | 查看帮助 | `/help` |
163
+ | 查看运行状态和上下文估计 | `/status` |
164
+ | 查看当前对话 | `/history` |
165
+ | 查看与切换对话分支 | `/tree`、`/checkout ID` |
166
+ | 在当前任务中追加要求 | 运行期间直接输入,或 `/steer 消息` |
167
+ | 当前任务完成后再处理 | `/follow 消息` |
168
+ | 取消当前任务 | Ctrl+C 或 `/abort` |
169
+ | 退出 | `/quit`,或空闲时 Ctrl+C |
170
+
171
+ 模型响应数据的空闲等待上限默认 300 秒。需要增加时可使用
172
+ `citrus --model-timeout 600`;它和命令工具自身的执行超时独立。
173
+
174
+ ## 在任意项目中启动
175
+
176
+ 安装为用户级命令,开发期间采用可编辑模式:
177
+
178
+ ```powershell
179
+ uv tool install --editable "D:\project\citrus-code"
180
+
181
+ # 在目标项目中运行,默认以当前目录作为工作目录
182
+ cd D:\project\poirot
183
+ citrus
184
+
185
+ # 或从任意目录指定工作目录
186
+ citrus --cwd "D:\project\poirot"
187
+ ```
188
+
189
+ 安装后无需激活 `.venv`。源码修改后重启命令即可;新增依赖时先退出 Citrus Code,再重新安装工具。全局安装和指定目录的详细说明见 [CLI 文档](https://github.com/big-orange947/citrus-code/blob/main/docs/cli.md#全局命令与工作目录)。
190
+
191
+ 上面的源码路径请替换为自己的克隆目录。如果安装后找不到命令,可执行 `uv tool update-shell`,然后重新打开终端。全局工具使用独立环境;需要严格使用项目的 `uv.lock` 时,在源码仓库中执行 `uv sync --locked` 和 `uv run citrus`。
192
+
193
+ ## 读取本地图片
194
+
195
+ CLI 为 `deepseek-flash`、`deepseek-v4-flash` 和 `deepseek-v4-flash-vision-exp` 启用图片输入。启动后提供图片路径,并让模型调用 `read`:
196
+
197
+ ```text
198
+ 请调用 read 读取 D:\screenshots\page.png,先描述图片内容,再分析布局问题。
199
+ ```
200
+
201
+ `read` 成功读取后才会把图片作为附件交给模型,单独输入路径不等于发送图片。当前 DeepSeek 接入允许 PNG、JPEG、GIF 和 WebP;其他格式需先转换,文件大小和尺寸仍受服务限制。第三方转发服务也需要支持相应的图片协议。
202
+
203
+ 当前不支持直接粘贴剪贴板位图、自动截图、缩放或转码。SVG 和 HTML 按源码读取;要分析渲染效果,请先生成截图,再提供截图路径。详见 [图片输入说明](https://github.com/big-orange947/citrus-code/blob/main/docs/cli.md#读取本地图片)。
204
+
205
+ ## 生成较大文件
206
+
207
+ 1M 上下文窗口与单次输出上限是两个限制。`write` 的文件正文、`edit` 的替换内容也占用模型输出预算。CLI 默认每次最多输出 32,768 tokens,可在模型服务允许的范围内调整:
208
+
209
+ ```powershell
210
+ citrus --cwd "D:\project\poirot" --max-output-tokens 65536
211
+
212
+ # 恢复会话时也可调整
213
+ citrus --cwd "D:\project\poirot" --resume latest --max-output-tokens 65536
214
+ ```
215
+
216
+ 模型以 `length` 结束时,界面显示“回复被截断”。该回复中的整批工具调用都不会执行,历史中记录截断诊断;此前已完成的调用不受影响。程序不自动补全残缺 JSON 或重复请求。可以继续会话,让模型先创建文件骨架,再分批编辑。详见 [截断处理](https://github.com/big-orange947/citrus-code/blob/main/docs/cli.md#生成较大文件与回复截断)。
217
+
218
+ ## 项目结构
219
+
220
+ ```text
221
+ citrus-code/
222
+ ├── src/citrus_code/
223
+ │ ├── config.py # 从环境变量加载连接配置
224
+ │ ├── __main__.py # python -m citrus_code 入口
225
+ │ ├── cli/ # 参数、组装、终端输入和事件展示
226
+ │ │ ├── main.py # 命令参数、应用组装和交互命令
227
+ │ │ ├── terminal.py # 异步输入、按键和状态栏
228
+ │ │ ├── render.py # Markdown、工具卡片和事件展示
229
+ │ │ ├── preview.py # 输入框上方的流式 Markdown 预览
230
+ │ │ └── theme.py # 字符标识、配色和终端文字清理
231
+ │ ├── ai/ # 模型调用层
232
+ │ │ ├── types.py # 内部消息、模型、上下文和工具定义
233
+ │ │ ├── events.py # 统一模型事件
234
+ │ │ ├── provider.py # Provider 接口
235
+ │ │ ├── registry.py # Provider 注册与查找
236
+ │ │ ├── completion.py # 等待完整模型响应
237
+ │ │ └── providers/
238
+ │ │ ├── fake.py
239
+ │ │ ├── deepseek.py
240
+ │ │ ├── chat_completions.py
241
+ │ │ ├── chat_stream_types.py
242
+ │ │ └── tool_call_buffer.py
243
+ │ ├── agent/ # 通用工具执行与运行时基础
244
+ │ │ ├── agent.py # 连续对话和状态维护
245
+ │ │ ├── loop.py # 模型与工具循环
246
+ │ │ ├── events.py
247
+ │ │ ├── state.py
248
+ │ │ ├── hooks.py
249
+ │ │ ├── context.py # 模型调用前的通用上下文准备接口
250
+ │ │ ├── types.py
251
+ │ │ └── tool_runner.py
252
+ │ └── coding/
253
+ │ ├── coding_session.py # 运行与持久化的应用层连接
254
+ │ ├── history.py # 工具调用与结果配对检查
255
+ │ ├── project_context.py # 读取工作目录下的 AGENTS.md
256
+ │ ├── context_hooks.py # 在模型请求中应用当前系统提示
257
+ │ ├── compaction/ # token 预算、近期交互选择和摘要生成
258
+ │ ├── session/ # 会话树、JSONL 存储和上下文重建
259
+ │ │ ├── types.py
260
+ │ │ ├── manager.py
261
+ │ │ ├── storage.py
262
+ │ │ ├── projection.py # 根据压缩记录重建模型上下文
263
+ │ │ └── recovery.py # 为缺失结果补充执行状态未知记录
264
+ │ └── tools/ # 具体 coding 工具
265
+ │ ├── factory.py # 默认、只读和全部工具组合
266
+ │ ├── read.py / write.py
267
+ │ ├── edit.py / edit_text.py
268
+ │ ├── bash.py / powershell.py / shell.py
269
+ │ ├── grep.py / find.py / ls.py
270
+ │ └── truncate.py / paths.py / mutation.py / processes.py
271
+ ├── docs/ # CLI、工具、会话和上下文压缩说明
272
+ ├── examples/ # 各学习阶段的独立演示
273
+ │ └── prototype/ # 早期原型存档,供对照阅读
274
+ ├── pyproject.toml
275
+ └── uv.lock
276
+ ```
277
+
278
+ 本地实验目录 `demo/`、会话目录 `.citrus-code/`、虚拟环境和 `.env` 均在 Git 忽略规则中,不随项目提交。
279
+
280
+ ## 开发环境与依赖
281
+
282
+ 源码克隆和首次启动见上方“快速开始”。后续拉取代码或修改依赖后,在仓库根目录同步环境:
283
+
284
+ ```powershell
285
+ uv sync --locked
286
+ ```
287
+
288
+ `uv sync --locked` 根据锁文件安装依赖。之后使用 `uv run python ...` 运行示例,无需手动激活虚拟环境。
289
+
290
+ 当前第三方依赖:
291
+
292
+ | 库 | 用途 |
293
+ | --- | --- |
294
+ | HTTPX | 异步 HTTP 请求和响应流读取 |
295
+ | Pydantic v2 | 数据类型校验、JSON 解析和 JSON Schema 生成 |
296
+ | prompt_toolkit | 异步终端输入、输入编辑和输入期间的输出展示 |
297
+ | Rich | Markdown、代码高亮、欢迎面板、工具卡片和终端排版 |
298
+
299
+ ## 先运行本地示例
300
+
301
+ 这些示例不需要 API Key,也不会调用远程模型。
302
+
303
+ ```powershell
304
+ # 观察模拟模型逐段输出
305
+ uv run python examples/stream_text.py
306
+
307
+ # 观察工具参数片段如何拼成完整调用
308
+ uv run python examples/tool_call_buffer.py
309
+
310
+ # 实际读取当前项目的 pyproject.toml
311
+ uv run python examples/read_tool.py
312
+
313
+ # 观察工具成功、参数错误和工具不存在时的统一处理
314
+ uv run python examples/tool_runner.py
315
+
316
+ # 在临时目录串起写入、编辑、读取、目录和搜索工具
317
+ uv run python examples/coding_tools.py
318
+
319
+ # 保存、加载会话,并恢复所选分支;结束后自动清理临时文件
320
+ uv run python examples/session_storage.py
321
+ ```
322
+
323
+ 搜索示例需要系统程序 `rg` 和 `fd`;可复用本机 pi 安装的版本。命令执行示例可以添加 `--shell bash` 或 `--shell powershell`。Python 依赖没有增加。
324
+
325
+ ## 配置 DeepSeek
326
+
327
+ 当前连接配置默认使用 DeepSeek 官方服务和 `deepseek-flash` 模型 ID。具体可用模型以 [DeepSeek 官方文档](https://api-docs.deepseek.com/quick_start/pricing/) 为准。
328
+
329
+ 在运行示例的 PowerShell 终端中设置:
330
+
331
+ ```powershell
332
+ $env:DEEPSEEK_API_KEY = "替换为你自己的 API Key"
333
+
334
+ # 以下两项可选,代码中已有默认值
335
+ $env:CITRUS_CODE_MODEL = "deepseek-flash"
336
+ $env:CITRUS_CODE_BASE_URL = "https://api.deepseek.com"
337
+ ```
338
+
339
+ 这些设置作用于当前终端会话及其启动的程序。当前代码只读取进程环境变量,不会自动加载 `.env` 文件。
340
+
341
+ ```powershell
342
+ # 查看配置;API Key 使用 SecretStr 遮盖显示
343
+ uv run python examples/show_config.py
344
+
345
+ # 查看真实模型的流式文字响应
346
+ uv run python examples/deepseek_raw_stream.py
347
+
348
+ # 通过 Provider、注册表和 complete() 取得完整响应
349
+ uv run python examples/deepseek_provider.py
350
+ ```
351
+
352
+ 后两个示例会实际调用模型,使用对应账户的 API 额度。当前接入显式关闭思考模式。
353
+
354
+ `deepseek_provider.py` 保留为仅演示模型接入的阶段示例。完整主循环见 `agent_state.py`;使用默认 coding 工具执行真实任务的示例为:
355
+
356
+ ```powershell
357
+ uv run python examples/coding_agent.py "读取 pyproject.toml,说明项目依赖"
358
+ ```
359
+
360
+ Windows 上如需使用 PowerShell,可添加 `--shell powershell`。上述示例会实际调用模型,工具也会按模型提出的调用执行。
361
+
362
+ ## 建议阅读顺序
363
+
364
+ | 阶段 | 源码 | 对应示例 | 重点 |
365
+ | --- | --- | --- | --- |
366
+ | 1 | `ai/types.py` | `convert_message.py` | 消息对象、工具定义、实际参数的区别 |
367
+ | 2 | `ai/events.py`、`ai/provider.py`、`providers/fake.py` | `stream_text.py` | `async`、`yield`、`async for` 和事件流 |
368
+ | 3 | `ai/registry.py`、`ai/completion.py` | `register_text.py` | 注册查找与流式接口的包装 |
369
+ | 4 | `config.py`、`providers/chat_completions.py` | `show_config.py`、`build_request.py`、`deepseek_raw_stream.py` | 环境变量、消息转换、HTTP 与 SSE |
370
+ | 5 | `providers/tool_call_buffer.py`、`providers/chat_stream_types.py` | `tool_call_buffer.py` | 工具索引、参数片段与完整 JSON 对象 |
371
+ | 6 | `providers/deepseek.py` | `deepseek_provider.py` | 把接入逻辑封装成统一 Provider |
372
+ | 7 | `agent/types.py`、`coding/tools/read.py`、`agent/tool_runner.py` | `read_tool.py`、`tool_runner.py` | 参数校验、实际执行、结果与错误反馈 |
373
+ | 8 | `agent/events.py`、`agent/loop.py`、`agent/agent.py`、`agent/state.py` | `agent_state.py` | 主循环、停止条件和连续对话状态 |
374
+ | 9 | `agent/hooks.py`、`agent/tool_runner.py` | `model_hooks.py`、`tool_hooks.py` | 调用前后扩展、拦截与结果处理 |
375
+ | 10 | `coding/tools/` | `coding_tools.py`、`coding_agent.py` | pi 风格工具、精确修改和输出控制 |
376
+ | 11 | `coding/session/` | `session_storage.py` | 消息树、追加存储、加载与分支恢复 |
377
+ | 12 | `coding/coding_session.py`、`coding/history.py`、`cli/` | `uv run citrus --fake` | 按事件保存、取消、消息队列与终端交互 |
378
+ | 13 | `coding/project_context.py`、`coding/context_hooks.py`、`cli/prompts.py` | CLI 启动时显示项目指令来源 | 项目约定、系统提示组装与历史消息的区别 |
379
+ | 14 | `coding/compaction/`、`coding/session/projection.py`、`agent/context.py` | 见上下文压缩说明 | 安全边界、完整工具交互、摘要与原始历史的区别 |
380
+
381
+ 上表中的源码路径以 `src/citrus_code/` 为基准,示例路径以 `examples/` 为基准。早期原型位于 `examples/prototype/`,仅保留作学习对照,不作为当前版本入口。
382
+
383
+ 学习时建议手写消息状态变化、参数拼接、执行边界和主循环;导入、重复类型声明和启动示例可以复制。重点是能够解释每一步的数据从哪里来、交给谁,以及失败后如何处理。
384
+
385
+ ## 后续计划
386
+
387
+ - [x] Agent 主循环:模型调用、工具执行、历史追加和停止条件。
388
+ - [x] Agent 状态封装与运行时事件。
389
+ - [x] Hooks:模型调用前后、工具执行前后的扩展点。
390
+ - [x] 写入、精确编辑、搜索和命令执行工具。
391
+ - [x] 工具输出截断与命令超时处理。
392
+ - [x] Agent 主动取消与嵌套事件流关闭。
393
+ - [ ] 工具增量事件。
394
+ - [x] 第一版 CLI 交互与终端流式展示代码。
395
+ - [x] 原创字符标识、Markdown 渲染、多行输入和状态栏。
396
+ - [x] 用户级命令安装与指定工作目录。
397
+ - [x] Flash 模型通过 read 工具接收本地图片。
398
+ - [x] 可配置模型输出上限,以及截断工具调用的执行保护。
399
+ - [ ] CLI 交互、取消和真实模型集成验证。
400
+ - [x] 独立会话存储层:保存、加载、分支选择和上下文重建。
401
+ - [x] 会话与 Agent 的集成及末尾缺失工具结果的恢复。
402
+ - [x] 启动时加载工作目录下的 AGENTS.md,并用于新会话和恢复会话的模型请求。
403
+ - [ ] 项目指令的父目录查找和运行中重新加载。
404
+ - [x] 1M 窗口下的 60% 自动压缩、近期完整交互保留和压缩记录重建。
405
+ - [ ] 上下文压缩的自动化与真实模型验证。
406
+ - [ ] 将各阶段整理成配套教程文档。
407
+
408
+ ## 许可证与参考项目
409
+
410
+ 本项目采用 [MIT 许可证](https://github.com/big-orange947/citrus-code/blob/main/LICENSE)。
411
+ 核心设计与部分实现参考 [pi](https://github.com/earendil-works/pi),许可证中保留其版权声明。
412
+ Citrus Code 是独立的 Python 学习项目。