craft-agents-oss v0.4.8 版本解析:call_llm工具、Skills 插件解析修复与 Codex 事件队列竞态修复
【免费下载链接】craft-agents-oss项目地址: https://gitcode.com/GitHub_Trending/cr/craft-agents-oss
本篇文章基于 craft-agents-oss 仓库 apps/electron/resources/release-notes/0.4.8.md 展开,系统讲解 v0.4.8(代号 "LLM Tool & Plugin Fix")的核心技术内容:主 Agent 如何通过新增的call_llm工具调用次级 LLM 完成摘要、分类、结构化提取等聚焦子任务,并深入剖析三个后端(Claude、Codex、Copilot)的接入方式;同时逐一拆解本版本修复的三个关键缺陷——Skill 插件名解析、Skill 热重载、Codex 事件队列竞态。读完本文,你将掌握call_llm的参数语义、结构化输出机制与附件处理约束,并理解其背后的源码实现与测试保障,可直接对照仓库源码进行二次开发或排障。
call_llm工具:为 Agent 注入次级 LLM 调用能力
v0.4.8 最核心的新增功能是call_llm工具——一个会话级(session-scoped)工具,允许主 Agent 在运行过程中调用另一个独立的 LLM来处理聚焦型子任务,例如:
- 摘要(summarization):对长文本或文件内容做浓缩提炼;
- 分类(classification):判定内容所属类别并给出置信度;
- 结构化提取(structured extraction):从非结构化文本中抽取实体、条目;
- 分析与对比(analysis / comparison):输出发现、问题、建议,或对比两段内容的异同。
该工具之所以有价值,核心在于三条设计动机(见 llm-tool.ts 中工具描述原文):
- 成本优化:简单任务(摘要、分类)可以指定更小、更便宜的模型,避免每次都动用主对话的大模型;
- 结构化输出:通过后端原生结构化输出能力保证 JSON Schema 合规,而非依赖提示词碰运气;
- 上下文隔离:把子任务内容交给次级 LLM 处理,不污染主对话的上下文窗口,同时支持并行调用——同一条消息里发起多个
call_llm会同时执行。
跨后端支持:Claude、Codex、Copilot
发布说明明确该工具在三个后端全部可用:
- Claude:走
@anthropic-ai/claude-agent-sdk,ClaudeAgent 实现了queryLlm()与runMiniCompletion()(见 claude-agent.ts); - Codex:通过 PreToolUse 拦截器与
buildCallLlmRequest共享预处理管线执行; - Copilot:同样经由共享的
buildCallLlmRequest管线(该函数注释明确写到 "Used by PiAgent's call_llm intercept path",见 llm-tool.ts)。
在鉴权模式上,发布说明区分了两档能力:
| 鉴权方式 | 功能范围 |
|---|---|
| API Key | 完整功能(full features) |
| OAuth | 基础功能(basic features) |
OAuth 路径下的实现示例可见 claude-agent.ts 附近注释 "queryLlm — Agent-native LLM query for call_llm tool (OAuth path)"。
参数语义与结构化输出
call_llm的完整参数由 llm-tool.ts 中的 Zod Schema 定义:
| 参数 | 类型 | 说明 |
|---|---|---|
prompt | string(必填) | 给次级 LLM 的指令,不能为空;正文内容应直接放入 prompt,不要通过 attachments 传内联文本 |
attachments | string 或{path, startLine?, endLine?}数组(最多 20 个) | 磁盘上已存在文件的路径,工具会自动加载内容;大文件可配合行区间截取 |
model | string(可选) | 模型 ID 或短名(如"haiku"、"sonnet"),默认使用快速摘要模型 |
systemPrompt | string(可选) | 可选的系统提示词 |
maxTokens | int 1–64000(可选) | 最大输出 token 数,默认 4096 |
temperature | 0–1(可选) | 采样温度 |
outputFormat | 枚举(可选) | 预定义输出格式:summary/classification/extraction/analysis/comparison/validation |
outputSchema | JSON Schema(可选) | 自定义结构化输出 Schema |
结构化输出有两种方式,且二者互斥(同时传outputFormat与outputSchema会返回错误):一是使用outputFormat选择内置格式,二是用outputSchema提供自定义 JSON Schema。源码中内置了六套预定义 Schema(见 llm-tool.ts),例如:
summary:要求返回{ summary, key_points, word_count? };classification:要求返回{ category, confidence, reasoning };extraction:要求返回{ items, count };analysis:要求返回{ findings, issues?, recommendations? }。
当传入 Schema 时,管线会把 JSON Schema 序列化后注入 system prompt,明确要求模型"仅返回符合该 Schema 的 JSON、不得附带其他文本或 markdown 格式"(见 llm-tool.ts),同时后端会尽量走原生结构化输出通道。
附件与文件加载约束
call_llm的附件机制定位是"传文件路径、工具自动加载内容"。其约束在 processAttachment 中有完整实现:
- 格式支持:文本文件,以及 png/jpg/jpeg/gif/webp 图片(但 Codex/Copilot 模式明确拒绝图片附件,报错提示 "Image attachments are not supported in ... mode. Use text files only.");
- 文件大小:单个文本文件 ≤ 500KB 或 ≤ 2000 行;超出时可改用
{path, startLine, endLine}行区间;全部附件合计 ≤ 2MB;图片 ≤ 5MB; - 路径解析:相对路径会基于会话目录(sessionPath)解析;
- 校验能力:文件不存在、权限拒绝、损坏的符号链接、目录误传、二进制内容(含 null 字节)、空文件、行区间非法(非正整数、start > end、区间超限)都会返回带可操作建议的错误信息——例如大文件错误中会附带按 imports/exports/functions/classes/tests/comments/config 分类的文件结构摘要,帮助 Agent 选择合适的行区间。
执行与超时
所有调用最终委托给各后端实现的queryLlm()(抽象方法定义见 base-agent.ts)。次级调用统一超时时间为120 秒(LLM_QUERY_TIMEOUT_MS,见 llm-tool.ts),通过Promise.race与超时定时器配合、并在完成后清理定时器(withTimeout)。未配置鉴权时,工具返回 "No authentication configured for call_llm" 的错误提示,引导用户先登录 AI 提供商。若结果带warning(如 SDK 在 max_turns 处停止),返回体前会标注[Partial result — ...],保证部分结果不被静默丢弃。
对应的测试覆盖见 packages/shared/src/agent/tests/pi-query-llm.test.ts(PiAgent.queryLlm 子进程 RPC 往返、超时、子进程退出时拒绝所有挂起调用)与 build-call-llm-request.test.ts。
修复一:Skill 插件名解析——不再依赖目录名
问题:当工作区目录名与 SDK 插件名(plugin name)不一致时,Skills 无法被正确解析。
根因:Claude SDK 识别插件时依据的是.claude-plugin/plugin.json清单中的name字段,而不是插件目录的path.basename()。旧实现可能退化为使用目录末段作为插件名,一旦目录名与清单中的name不一致,skill 的限定名(pluginName:skillSlug)就会错位,导致解析失败。
修复:新增readPluginName()(见 workspace.ts),从.claude-plugin/plugin.json读取真实插件名,不可读时返回 null;extractWorkspaceSlug()(workspace.ts)优先使用该真实插件名,仅在无清单时回退到路径末段(legacy 行为)。系统提示词构建(system.ts)与 skill 限定(pre-tool-use.ts)均改为使用这一真实名称。
测试用例见 workspace-slug.test.ts:覆盖 plugin.json 存在且含 name、清单缺失、name 字段缺失、清单为非法 JSON 等四种情形。
修复二:Skill 热重载——三层列表不再"消失"
问题:在工作区中添加一个 skill 后,全局(global)和项目(project)级 skill 会一起消失,直到重启应用。
根因:部分重载路径只返回了工作区这一层的 skill 列表,覆盖(替换)了原本完整的三层列表。
修复:所有重载路径统一改用loadAllSkills,返回完整三层列表。源码中loadAllSkills(workspaceRoot, projectRoot?)每次调用最多读取三个目录(storage.ts):
- Workspace 层:
{workspaceRoot}/skills/{slug}/,插件名取自 plugin.json; - Project 层:
{workingDir}/.agents/skills/{slug}/,插件名为.agents; - Global 层:
~/.agents/skills/{slug}/,插件名同样为.agents(见 storage.ts 与 pre-tool-use.ts)。
同名 slug 按 project > workspace > global 优先级覆盖去重。测试 storage.test.ts 覆盖了完整三层加载、三层同名覆盖、projectRoot 缺省时跳过项目层、跨层去重等场景,是"热重载后三层列表完整保留"这一行为的最佳验证。
修复三:Codex 事件队列竞态——工具结果不再丢失
问题:Codex 后端中,当异步的item/completed事件处理器仍在运行时turn/completed到达,工具结果与助手文本可能丢失。
根因:事件队列在turn/completed到达时即标记完成,未等待仍在飞行中的item/completed处理器收尾,导致后到的事件被清空/丢弃。
修复:将队列的"完成"推迟到所有处理器执行完毕之后再触发。事件队列的同步机制见 event-queue.ts:enqueue()入队并唤醒等待者,complete()标记完成,只有当队列已清空且完成标记已置位时才真正判定 turn 结束(isTurnComplete返回done && queue.length === 0)。保证"结果先于完成信号落地",避免工具结果和助手文本丢失。
内部改进:Copilot 后端补全与 UI 徽章
本版本还有三项偏内部(Internal)的改动:
- Copilot
runMiniCompletion现已可用:runMiniCompletion(prompt)是各后端共有的抽象方法(types.ts、base-agent.ts),用于标题生成、摘要等快速文本任务。Claude 侧实现见 claude-agent.ts(无工具、空系统提示、单轮、禁用 thinking);Pi 侧实现通过子进程 RPCmini_completion消息完成,超时同样对齐 120 秒(pi-agent.ts)。Copilot 后端的runMiniCompletion恢复可用后,标题生成功能在 Copilot 后端被激活。 - Copilot 事件适配器抑制 reasoning/intent 事件:避免内部推理/意图事件泄漏到对外事件流中。
call_llm模型徽章:TurnCard 活动行中新增模型徽章展示(TurnCard.tsx),当工具名为mcp__session__call_llm且传入model参数时,在活动行内以徽章形式显示所用模型(TurnCard.tsx),让调用次级 LLM 时使用的模型一目了然。
总结
v0.4.8 是一次"功能 + 稳定性"并重的版本:call_llm工具为 Agent 带来了低成本、可并行、原生结构化输出的次级 LLM 调用通道,并在 Claude / Codex / Copilot 三大后端与 API Key / OAuth 两种鉴权模式下统一落地;三个 Bug 修复则分别解决了 Skill 插件名解析、Skill 热重载丢列表、Codex 事件竞态丢结果这三类直接影响日常使用体验的问题。开发者如需深入,可重点阅读 llm-tool.ts、workspace.ts、storage.ts 及对应的测试文件,源码结构与测试用例可完整还原本版本的每一次行为变更。
【免费下载链接】craft-agents-oss项目地址: https://gitcode.com/GitHub_Trending/cr/craft-agents-oss
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考