-
Notifications
You must be signed in to change notification settings - Fork 3
Direct Engine.zh CN
🌐 Language: English | 中文
内置 ReAct loop。src/main/AgentLoop.ts 是核心,搭配 src/main/glm.ts(provider)、src/main/tools.ts(工具)。
runAgentLoop(opts)(AgentLoop.ts:23):
messages = [system, ...memMsg?, ...history, userInput]
loop:
1. provider.streamComplete(messages, tools)
2. push assistant 回应
3. 没有 tool_calls → done,return
4. 跑工具(runToolBatch),push tool 结果
5. 回到 1
默认 maxTurns = ∞(AgentLoop.ts:26)。模型不收敛(一直 tool_call)会持续消耗,需手动点停止。原来是 50,改成 ∞ 是为了让复杂任务不被中途砍断。
DirectEngine.run(engines.ts:91)拼 systemPrompt:
baseSystemPrompt
+ skillSection(用户用 / 调用的 skill body)
+ loadProjectRules(AGENTS.md / CLAUDE.md)
+ rulesBlock(KINET.md,app 维护)
+ contextBlock(KINET-CONTEXT.md)
注意:memoryBlock 不在 systemPrompt 里。从 v1.0 起改走 history[0] 注入(见下)。
memoryBlock 走 history0
长期记忆块作为一条 user 消息插在 history 头部,带 _memory: true 标记。理由:
- Anthropic 的
cache_control标在整个 system 上。memory 拼进 system 时,记忆每变一次就打穿整个 base+rules+context 系统缓存。 - 拆出来后,system 跨轮稳定,缓存命中;记忆只在自身变化时失效(本来就少量 token)。
代码:
-
AgentLoop.ts:36-39——memMsg仅在当轮messages里出现 -
dropTransient(AgentLoop.ts:98)—— 在 return 时过滤 system +_memory,不写回directHistory(否则会跨轮堆积、变陈旧) -
trimHistoryToTokenBudget(AgentLoop.ts:131)——_memory消息永远保留,不参与预算裁剪 -
compactHistory(AgentLoop.ts:158)——_memory不参与头部摘要 -
glm.tsOpenAI provider 序列化前剥掉_memory(避免发到 API);Anthropic provider 合并连续 user 消息(满足严格交替)
详见 Long-Term-Memory。
runToolBatch(AgentLoop.ts:197):
-
连续只读工具并发(read_file / grep / glob / web_fetch / recall_memory / git_diff / dispatch_agent)——
Promise.all一起跑 - 写工具串行(shell / write_file / edit_file)—— 防竞态
- 结果按原
toolCalls顺序回填(tool_call_id配对) - abort 时补「[已停止]」占位以维持配对(OpenAI 和 Anthropic 都拒绝孤儿 tool 消息)
- UI 拿完整原文;模型拿截断版(见下)
truncateForModel(AgentLoop.ts:250):
- 头尾各 3000 字符
- 中间替换成
…[省略 N 字符;UI 步骤详情可见完整结果]… - 阈值 8192 字符
为什么:一个 4MB 文件 / 几 MB shell 输出 / web_fetch 全页不截的话,下一轮全字面进 input token,爆。模型基本只需头尾(路径/错误/概要)。真要全文可加 follow-up 让 read_file 偏移读。
tokenCoef(AgentLoop.ts:113):初始 0.6(中英混合经验值)。
每轮拿 API 真实 prompt_tokens 反推 token/char 比,滑动平均 0.5/0.5(calibrateTokens)。自动贴合实际模型(GLM/Claude/OpenAI 的 tokenizer 各不同且不公开 → 不上 tiktoken,加 ~1MB 依赖、打包变大)。
estMsgChars:content.length + JSON.stringify(tool_calls).length。tool_calls 之前漏算 → 大 tool result 误判余量、超发。
API 报 context-too-long 时(AgentLoop.ts:44):
isContextTooLong(e) → 砍半预算(15K)trim history → 重试本轮一次
isContextTooLong 是 best-effort:match context length|too long|maximum context|上下文|exceed|prompt is too 等关键字 + HTTP 413。只重试一次(ponytail:没做更激进的递归回退,够用)。
compactHistory(AgentLoop.ts:158):
- turn 末尾按需(超 30K token)调一次
- tail 保留完整轮次(
trimHistoryToTokenBudget) - head 调一次 LLM 压成「[早期对话摘要]」user 消息
- 摘要 prompt 保留:任务目标、关键决策、已确定的结论、重要的文件路径/命令/技术栈
- 失败 → 回退纯尾部 trim(不丢功能)
ponytail::① 每 turn 末尾按需摘一次,未做摘要缓存(同段历史可能被反复摘要);② head 只取 slice(0, 12_000) 字符(超长历史头部摘要不全)。
工具 dispatch_agent(tools.ts)派发一个子任务:
- 复用
runAgentLoop,独立 history - 只读工具(
readOnlyTools()) - maxTurns 限 8
- 事件只转发 cost(也花钱)+ tool(带前缀供 UI 观感),吞掉 token 防刷屏
- 返回值 = 子 agent 的 assistant 文本输出
主 agent 不看到子 agent 的中间过程,只看到最终文本 —— 隔离上下文,避免主对话被刷屏。
src/main/glm.ts:
-
OpenAICompatibleProvider ——
/chat/completions+ Bearer,自动前缀缓存(GLM 智谱 / DeepSeek / Qwen / OpenAI 端点都自动) -
AnthropicProvider ——
/v1/messages+ x-api-key + anthropic-version,显式cache_control断点(system + 末个 tool,覆盖整个 tools 数组)
两个 provider 都把响应 normalize 成同一个 Completion:
{ content: string; toolCalls: ToolCall[]; rawAssistant: ChatMsg; tokensIn: number; tokensOut: number }rawAssistant 是 OpenAI-format,无论原始协议是什么 → AgentLoop history 协议无关。
fetchUntil200(glm.ts:62):
- 可重试状态码:
429 / 500 / 502 / 503 / 529 - 指数退避:1s / 2s / 4s,±25% jitter
- 最多 3 次
- SSE 流一旦开始就不能重试(会重复 token),所以重试只覆盖「建连 → 拿到 200 响应」这一段
priceUSD(model, tokensIn, tokensOut)(glm.ts:96):
- GLM 系列用 GLM 价(
0.00000007 / tokenin) - 其他用 OpenAI 默认价(
0.000003 / tokenin) - ⚙ → 价格 可以覆盖(API key 旁边)
Anthropic cache token 计费:cache_read_input_tokens 按 input 价算(实际 ~10%,高估但比漏掉好);cache_creation_input_tokens 按 input 价算(实际 ~125%)。
-
src/main/AgentLoop.ts—— ReAct loop + trim + compact -
src/main/engines.ts:91——DirectEngine.run -
src/main/glm.ts—— provider + 流式 -
src/main/tools.ts—— 10 个工具