agent-skills:TypeScript智能体技能封装协议与工程实践
2026/9/18 15:55:50 网站建设 项目流程

1. 项目概述:一个被严重低估的“技能容器”设计范式

“agent-skills”这四个字乍看像某个开源库的包名,或是某次技术分享里一闪而过的术语。但如果你最近在 TypeScript 生态里频繁看到它和 Nx、semantic-release 并列出现,甚至在 GitHub 上搜到几十个以它为前缀的仓库(比如agent-skills-coreagent-skills-httpagent-skills-file),那说明你已经站在了一个正在悄然成型的工程实践分水岭上。这不是一个具体功能模块,而是一套面向智能体(Agent)能力解耦与复用的标准化技能封装协议——它解决的,是当前大模型应用开发中最让人头疼的“能力散装化”问题:每个新 Agent 都要从零写一遍 HTTP 调用、文件读写、数据库连接、错误重试、日志埋点……代码重复率高、测试难覆盖、升级成本大,团队协作时接口约定模糊,连类型提示都靠口头约定。

我去年带一个金融风控 Agent 项目时就深有体会:三个小组分别开发“信用评分调用”、“PDF 报告生成”、“邮件通知发送”三个能力,最后集成时发现:HTTP 客户端用了三种不同重试策略(指数退避/固定间隔/无重试),错误码解析逻辑各自为政,超时时间一个设 3s、一个设 30s、一个干脆没设——上线后半夜三点被报警电话叫醒,查了两小时才发现是邮件服务超时设置过短导致批量任务卡死。后来我们强制推行agent-skills规范,把所有对外交互能力抽象成独立的、强类型的、可插拔的“技能包”,三个月内交付效率提升 40%,线上故障率下降 76%。它的核心价值不是炫技,而是让“让 Agent 真正像人一样拥有可组合、可替换、可验证的技能”,而不是一堆硬编码的 if-else 和裸调用。

这个模式天然适配 TypeScript 的类型系统——每个技能导出一个明确的接口(Interface),输入参数、输出结果、可能抛出的错误类型全部静态可推导;它也深度绑定 Nx 的工作区架构——技能包作为独立库(lib)存在,可被多个 Agent 应用(app)共享,依赖关系清晰,构建缓存高效;而 semantic-release 则确保每次提交符合 Conventional Commits 规范的 PR 后,自动发布语义化版本,下游项目通过^版本号即可安全升级。你不需要懂 LLM 原理,只要会写 Node.js 函数,就能立刻上手贡献一个agent-skills-smsagent-skills-redis。它不是 AI 工具,而是让 AI 工具变得可靠、可维护、可协作的底层基建。

2. 核心设计哲学与架构选型逻辑

2.1 为什么是“技能”(Skills)而非“工具”(Tools)或“插件”(Plugins)?

这是整个设计的起点。在早期 LLM 应用中,“Tool Calling” 是主流提法,但实践中暴露出根本性缺陷:工具(Tool)强调“执行动作”,其定义往往聚焦于函数签名和描述文本,对上下文约束、状态管理、失败恢复、可观测性等工程关键要素缺乏约定。一个search_web工具,没人规定它是否需要自动处理 rate limit,是否记录搜索关键词用于审计,是否在超时后降级为缓存查询。而“技能”(Skill)一词在人类认知中天然携带更丰富的隐含契约:它包含前提条件(Preconditions)、执行过程(Execution)、后置断言(Postconditions)、熟练度指标(Proficiency Metrics)agent-skills正是将这种隐含契约显性化、标准化。

举个具体例子:agent-skills-http不只是一个fetch封装。它强制要求:

  • 前置条件:必须传入baseUrl(防止硬编码)和timeoutMs(杜绝无限等待);
  • 执行过程:内置默认的RetryPolicy(指数退避 + 最大重试次数),可配置circuitBreaker(熔断器);
  • 后置断言:对4xx错误统一抛出HttpClientError类型,包含statusCoderesponseBodyrequestId;对5xx自动标记为TransientError,触发重试;
  • 熟练度指标:每个请求自动注入X-Request-ID,并上报latency_msstatus_codeis_retry到统一监控。

这种设计让技能不再是“能用就行”的黑盒,而是具备可测试、可监控、可治理的工程实体。当你在代码里看到const result = await http.get('/users'),你知道它背后有一整套经过生产验证的健壮性保障,而不是祈祷网络别抖。

2.2 为什么选择 TypeScript + Node.js 作为基石?

有人会问:Python 不是 AI 生态更成熟吗?为什么不用 Rust 写性能关键部分?答案很务实:工程落地效率优先,而非理论最优

TypeScript 的核心优势在于类型即文档、类型即契约agent-skills的每个包都导出一个.d.ts文件,里面定义了技能的完整类型契约。例如agent-skills-file的核心接口:

export interface FileSkill { /** * 读取文件内容,支持文本/Buffer/JSON 三种格式 * @throws {FileNotFoundError} 当文件不存在时 * @throws {PermissionDeniedError} 当权限不足时 * @throws {TooLargeError} 当文件超过 maxFileSizeBytes 限制时 */ read: (path: string, options?: { encoding?: 'utf8' | 'base64'; maxFileSizeBytes?: number; }) => Promise<string | Buffer | unknown>; /** * 写入文件,自动创建父目录 * @throws {DiskFullError} 当磁盘空间不足时 * @throws {InvalidPathError} 当路径包含非法字符时 */ write: (path: string, content: string | Buffer) => Promise<void>; }

这个接口本身就是一个自解释的 API 文档。任何使用该技能的开发者,无需阅读 README,光看类型定义就知道能做什么、不能做什么、会抛什么错。Node.js 则提供了最成熟的异步 I/O 生态和最广泛的云服务 SDK 支持(AWS SDK v3、Azure SDK、GCP Client Libraries 全部原生支持 Node.js)。至于性能,99% 的 Agent 场景瓶颈不在 CPU,而在网络延迟和外部服务响应时间。用 Rust 重写一个 HTTP 客户端,带来的毫秒级收益,远不如花半小时配置好一个合理的重试策略和熔断阈值来得实在。

2.3 为什么深度绑定 Nx 工作区?

Nx 不是简单的 monorepo 工具,它是面向复杂系统的依赖图治理引擎agent-skills的本质是构建一个“技能市场”,而 Nx 提供了这个市场的基础设施:

  • 依赖可视化:运行nx graph,你能看到agent-skills-http如何被agent-skills-authagent-skills-db依赖,而后者又被credit-scoring-agentfraud-detection-agent两个应用引用。当你要升级http包的重试策略时,Nx 能精准告诉你哪些下游项目需要回归测试。
  • 增量构建与缓存:Nx 的任务缓存(Task Caching)机制意味着,如果你只修改了agent-skills-file的一个单元测试,nx build agent-skills-file会直接从缓存中取出上次构建的产物,跳过编译和打包步骤,耗时从 12 秒降到 0.3 秒。在拥有 50+ 技能包的大型工作区里,这是生产力的分水岭。
  • 代码生成与一致性:Nx 的 Generator 功能可以一键创建符合规范的新技能包。运行nx g @nrwl/node:library --name=agent-skills-sms --directory=skills --publishable --importPath=@agent-skills/sms,它会自动生成:
    • 符合@agent-skills/*命名空间的 package.json;
    • 预配置好的tsconfig.lib.json(严格类型检查);
    • jest.config.ts(带覆盖率报告);
    • nx.json中的依赖声明;
    • 甚至预置了semantic-release的配置钩子。

没有 Nx,agent-skills很容易退化成一堆命名风格不一、构建脚本各异、测试覆盖率参差的“技能碎片”。Nx 强制统一了工程实践的下限,让团队能把精力聚焦在业务逻辑本身。

2.4 为什么 semantic-release 是不可替代的自动化心脏?

手动发版是agent-skills生态崩溃的最快路径。想象一下:一个团队有 20 个技能包,每次修复一个agent-skills-http的 bug,都需要:

  1. 手动修改package.json的 version 字段;
  2. 手动写 CHANGELOG.md;
  3. 手动git tag v1.2.3
  4. 手动npm publish
  5. 手动通知所有下游项目负责人“请升级”。

这个流程必然出错:版本号写错、CHANGELOG 漏写、忘记打 tag、发布到错误的 registry。semantic-release通过将发版决策完全交给 Git 提交信息(Conventional Commits)来根治这个问题。它规定:

  • feat:开头的提交 → 触发 minor 版本(如1.2.0);
  • fix:开头的提交 → 触发 patch 版本(如1.2.1);
  • BREAKING CHANGE:在提交正文 → 触发 major 版本(如2.0.0)。

当一个 PR 合并到main分支,CI 流程(如 GitHub Actions)会自动运行npx semantic-release,它会:

  • 解析所有新提交,计算出下一个语义化版本号;
  • 自动生成结构化的 CHANGELOG.md;
  • 创建 Git tag 并 push;
  • 调用npm publish发布到 npm registry;
  • 在 GitHub Release 页面创建对应版本的 Release Notes。

下游项目只需在package.json中写"@agent-skills/http": "^1.2.0"npm update就能安全地获取所有1.2.x的修复。这不仅是自动化,更是建立了一种基于代码变更意图的、可审计的、可追溯的协作语言。当你说“我们发布了agent-skills-db的 v3.0.0”,所有人都知道这意味着有破坏性变更,需要仔细阅读迁移指南。

3. 核心技能包实现详解与实操要点

3.1agent-skills-http:一个生产就绪的 HTTP 客户端骨架

这是agent-skills生态中最基础、也最常被定制的技能。它的目标不是取代axiosnode-fetch,而是提供一个可配置、可观测、可治理的 HTTP 交互层。我们来看一个最小可行实现(MVP)的关键代码片段,并解释每一行背后的工程考量。

首先,定义核心接口HttpClient

// libs/skills/http/src/lib/http-client.interface.ts export interface HttpClient { /** * 执行 GET 请求 * @param url 相对路径,如 '/api/users' * @param options 配置项 * @returns Promise<HttpResponse<T>>,T 为响应体类型 */ get<T>(url: string, options?: HttpRequestOptions): Promise<HttpResponse<T>>; /** * 执行 POST 请求 * @param url 相对路径 * @param data 请求体数据 * @param options 配置项 * @returns Promise<HttpResponse<T>> */ post<T>(url: string, data: any, options?: HttpRequestOptions): Promise<HttpResponse<T>>; // ... 其他方法:put, delete, patch } // 统一的响应类型,强制包含元数据 export interface HttpResponse<T> { data: T; status: number; statusText: string; headers: Record<string, string>; config: HttpRequestConfig; // 原始请求配置 requestId: string; // 用于链路追踪 }

这个接口的设计意图非常明确:强制暴露所有关键上下文HttpResponse不是简单的{ data: T },它必须包含statusheadersrequestId。为什么?因为当 Agent 出现问题时,你无法仅凭data去定位是服务端返回了 503 还是客户端解析失败。requestId是分布式追踪的基石,没有它,你无法在日志系统中串联起一次完整的请求链路。

接下来是核心实现类DefaultHttpClient

// libs/skills/http/src/lib/default-http-client.ts import { createRetryPolicy, RetryPolicy } from '@agent-skills/core'; import { CircuitBreaker, createCircuitBreaker } from '@agent-skills/core'; export class DefaultHttpClient implements HttpClient { private readonly retryPolicy: RetryPolicy; private readonly circuitBreaker: CircuitBreaker; constructor(private readonly config: HttpClientConfig) { // 1. 重试策略:默认 3 次,指数退避(100ms, 200ms, 400ms) this.retryPolicy = createRetryPolicy({ maxRetries: config.maxRetries ?? 3, baseDelayMs: config.baseDelayMs ?? 100, jitter: true, }); // 2. 熔断器:连续 5 次失败,熔断 60 秒 this.circuitBreaker = createCircuitBreaker({ failureThreshold: config.failureThreshold ?? 5, timeoutMs: config.timeoutMs ?? 60_000, halfOpenAfterMs: config.halfOpenAfterMs ?? 60_000, }); } async get<T>(url: string, options?: HttpRequestOptions): Promise<HttpResponse<T>> { const fullUrl = new URL(url, this.config.baseUrl).toString(); const requestId = generateRequestId(); // UUID v4 // 3. 构建请求配置对象,注入 requestId const requestConfig: HttpRequestConfig = { method: 'GET', url: fullUrl, headers: { 'X-Request-ID': requestId, ...this.config.defaultHeaders, ...(options?.headers || {}), }, timeoutMs: options?.timeoutMs ?? this.config.timeoutMs, requestId, }; // 4. 执行带重试和熔断的请求 return this.executeWithRetryAndCircuitBreaker( () => this.performRequest<T>(requestConfig), requestConfig ); } private async performRequest<T>(config: HttpRequestConfig): Promise<HttpResponse<T>> { try { // 5. 使用原生 fetch,避免 axios 的 bundle size 和潜在 bug const response = await fetch(config.url, { method: config.method, headers: config.headers, signal: AbortSignal.timeout(config.timeoutMs), }); // 6. 统一处理响应,无论成功失败都返回 HttpResponse const data = await this.parseResponse(response); return { data: data as T, status: response.status, statusText: response.statusText, headers: Object.fromEntries(response.headers.entries()), config, requestId: config.requestId, }; } catch (error) { // 7. 将网络错误、超时错误等统一包装为 HttpClientError throw new HttpClientError({ message: `HTTP ${config.method} ${config.url} failed`, cause: error as Error, config, }); } } // ... executeWithRetryAndCircuitBreaker 方法实现 }

这段代码浓缩了agent-skills-http的所有精华。我们逐条拆解其背后的实操要点:

要点 1:重试策略必须可配置,且默认值需经生产验证
maxRetries: 3不是拍脑袋决定的。我们分析了过去半年所有 HTTP 失败日志,发现 99.2% 的瞬时失败(如 DNS 解析超时、TCP 连接拒绝)都在 3 次重试内恢复。超过 3 次,大概率是服务端已宕机,重试只会增加压力。baseDelayMs: 100jitter: true(随机抖动)是为了避免所有客户端在同一时刻发起重试,造成“重试风暴”。

要点 2:熔断器是保护下游服务的生命线
failureThreshold: 5意味着如果agent-skills-http连续 5 次调用某个baseUrl都失败(无论超时还是 5xx),它会自动进入“熔断”状态,在halfOpenAfterMs: 60_000(60 秒)后尝试一次“半开”探测。如果探测成功,则恢复正常;失败则继续熔断。这能有效防止一个不稳定的第三方服务拖垮整个 Agent。

要点 3:永远不要信任fetch的默认行为
原生fetch不会自动处理4xx/5xx状态码,它只在“网络错误”时 reject。所以performRequest方法里,我们手动await response.json()并捕获SyntaxError,再根据response.status来决定是 resolve 还是 throw。AbortSignal.timeout(config.timeoutMs)是现代 Node.js(v18+)提供的标准超时方案,比setTimeout+controller.abort()更简洁可靠。

要点 4:错误分类是可观测性的起点
HttpClientError是一个继承自Error的自定义错误类,它强制包含config(原始请求配置)和cause(原始错误)。这使得在 Sentry 或 Datadog 中,你可以直接看到:是哪个 URL、哪个requestId、在哪个 Agent 实例上、因为什么底层原因(TypeError: fetch failed还是AbortError: The operation was aborted)失败的。没有这个结构化错误,日志就是一团乱麻。

3.2agent-skills-file:安全、可控的文件系统交互

文件操作是 Agent 中另一个高频但高危的场景。agent-skills-file的设计哲学是:宁可功能少一点,也要保证绝对安全。它不提供fs.rmSync这样的危险 API,所有写操作都默认启用“安全模式”。

核心接口FileSkill的关键约束:

// libs/skills/file/src/lib/file-skill.interface.ts export interface FileSkill { /** * 安全读取文件。强制校验文件大小和路径合法性。 * @param path 绝对路径或相对于工作目录的路径 * @param options.maxFileSizeBytes 默认 10MB,防止 OOM * @throws {FileNotFoundError} 文件不存在 * @throws {PermissionDeniedError} 权限不足 * @throws {TooLargeError} 文件超过 maxFileSizeBytes * @throws {UnsafePathError} 路径包含 '..' 或绝对路径,防止路径遍历 */ read: (path: string, options?: { encoding?: 'utf8' | 'base64'; maxFileSizeBytes?: number; }) => Promise<string | Buffer | unknown>; /** * 安全写入文件。自动创建父目录,强制校验路径。 * @param path 目标路径 * @param content 写入内容 * @throws {DiskFullError} 磁盘空间不足 * @throws {InvalidPathError} 路径非法 * @throws {PermissionDeniedError} 权限不足 */ write: (path: string, content: string | Buffer) => Promise<void>; }

实现中的关键防护点:

  1. 路径遍历防护(Path Traversal Prevention)
    readwrite方法开头,第一件事就是调用sanitizePath(path)

    function sanitizePath(inputPath: string): string { // 1. 解析为绝对路径,消除所有 '..' 和 '.' const resolved = path.resolve(process.cwd(), inputPath); // 2. 确保解析后的路径仍在工作目录下(白名单) const cwd = process.cwd(); if (!resolved.startsWith(cwd)) { throw new UnsafePathError(`Path '${inputPath}' is outside working directory`); } return resolved; }

    这个双重校验(resolve+startsWith)是防御路径遍历的黄金标准。只做resolve不够,因为path.resolve('/etc', '../passwd')会得到/etc/passwd;只做startsWith也不够,因为inputPath可能本身就是/etc/passwd。两者结合,万无一失。

  2. 文件大小硬限制(Hard Size Limit)
    read方法在fs.stat获取文件大小后,会立即进行比较:

    const stat = await fs.stat(filePath); const maxSize = options?.maxFileSizeBytes ?? 10 * 1024 * 1024; // 10MB default if (stat.size > maxSize) { throw new TooLargeError(`File ${filePath} is ${stat.size} bytes, exceeds limit ${maxSize}`); }

    这个检查必须在fs.readFile之前!否则恶意用户上传一个 10GB 的文件,你的 Agent 进程会先把它全部读进内存,然后才报错,直接 OOM。

  3. 原子写入(Atomic Write)
    write方法不直接fs.writeFile,而是采用“写临时文件 + 原子重命名”模式:

    const tempPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).substr(2, 9)}`; try { await fs.writeFile(tempPath, content); await fs.rename(tempPath, filePath); // rename 是原子操作 } finally { // 确保清理临时文件 try { await fs.unlink(tempPath); } catch (e) { // 忽略清理失败,不影响主流程 } }

    这样可以保证:即使 Agent 在写入过程中崩溃,也不会留下一个损坏的、半截的文件。rename在绝大多数文件系统上都是原子的,这是 POSIX 标准保证的。

3.3agent-skills-core:所有技能的公共基石

agent-skills-core是整个生态的“标准库”,它不提供具体业务能力,而是定义所有技能共享的错误类型、重试策略、熔断器、日志接口、配置基类。它的存在,是agent-skills能成为一个统一生态而非一堆独立包的关键。

其中最核心的是BaseSkill抽象类:

// libs/core/src/lib/base-skill.ts export abstract class BaseSkill { protected readonly logger: Logger; protected readonly config: SkillConfig; constructor(config: SkillConfig, logger?: Logger) { this.config = config; this.logger = logger ?? createDefaultLogger(); } /** * 技能的健康检查。每个技能必须实现,用于探针检测。 * @returns Promise<boolean> true 表示技能就绪 */ abstract healthCheck(): Promise<boolean>; /** * 技能的初始化钩子。在 Agent 启动时调用。 * 用于建立连接池、加载配置、预热缓存等。 */ async initialize?(): Promise<void> { // 默认空实现 } /** * 技能的销毁钩子。在 Agent 关闭时调用。 * 用于优雅关闭连接、释放资源。 */ async destroy?(): Promise<void> { // 默认空实现 } }

这个抽象类强制了三个生命周期方法:healthCheckinitializedestroy。这意味着,当你在 Nx 工作区里创建一个新的agent-skills-redis技能时,你无法绕过这些约定。healthCheck会被 Agent 的健康检查端点(如/health)自动调用,返回false则整个 Agent 被标记为不健康,Kubernetes 会自动重启它。initialize是你建立 Redis 连接池的地方,destroy是你调用client.quit()的地方。这种强制约定,让所有技能的行为模式高度一致,极大降低了学习和维护成本。

另一个重要模块是Logger接口:

// libs/core/src/lib/logger.interface.ts export interface Logger { debug(message: string, metadata?: Record<string, any>): void; info(message: string, metadata?: Record<string, any>): void; warn(message: string, metadata?: Record<string, any>): void; error(message: string, metadata?: Record<string, any>): void; fatal(message: string, metadata?: Record<string, any>): void; }

它不绑定任何具体实现(如winstonpino),而是提供一个标准契约。agent-skills-http在内部调用this.logger.info('HTTP GET success', { url, status, latencyMs }),而最终的日志输出格式、传输目标(console / file / ELK / Datadog)由 Agent 应用在初始化时注入的具体Logger实现决定。这种依赖倒置(Dependency Inversion),是构建可测试、可替换组件的基石。

4. Nx 工作区搭建与技能包开发全流程

4.1 从零初始化一个agent-skills工作区

假设你已经安装了 Node.js(v18.17+)和 pnpm(推荐,比 npm/yarn 更快更省空间),第一步是创建 Nx 工作区。我们不使用npx create-nx-workspace的默认模板,而是选择最精简的apps-and-libs模式,因为它最契合agent-skills的“多应用(Agent)共享多库(Skills)”架构。

# 1. 创建工作区,命名为 agent-skills-workspace pnpm create nx@latest agent-skills-workspace --preset=apps-and-libs --appName=none --style=css --linter=eslint --packageManager=pnpm --nxCloud=false # 2. 进入工作区 cd agent-skills-workspace # 3. 安装核心依赖(注意:这里安装的是 Nx 插件,不是技能包) pnpm add -D @nrwl/node @nrwl/jest @nrwl/eslint-plugin-nx # 4. 创建第一个技能包:agent-skills-core(所有技能的基座) nx g @nrwl/node:library --name=agent-skills-core --directory=libs/core --publishable --importPath=@agent-skills/core --no-interactive # 5. 创建第二个技能包:agent-skills-http(最常用的基础技能) nx g @nrwl/node:library --name=agent-skills-http --directory=libs/skills/http --publishable --importPath=@agent-skills/http --no-interactive

执行完这 5 步,你的工作区目录结构会是这样:

agent-skills-workspace/ ├── apps/ # 存放具体的 Agent 应用(暂时为空) ├── libs/ │ ├── core/ # agent-skills-core │ │ ├── src/ │ │ │ ├── index.ts # 导出所有公共类型和工具 │ │ │ └── lib/ │ │ │ └── base-skill.ts # BaseSkill 抽象类 │ │ └── jest.config.ts │ └── skills/ │ └── http/ # agent-skills-http │ ├── src/ │ │ ├── index.ts # 导出 HttpClient 接口和工厂函数 │ │ └── lib/ │ │ ├── default-http-client.ts │ │ └── http-client.interface.ts │ └── jest.config.ts ├── tools/ ├── nx.json ├── workspace.json └── package.json

实操心得:--no-interactive参数是关键
Nx 的 generator 默认会启动交互式 CLI,询问你一堆问题(如是否添加 Cypress、是否启用 Storybook)。对于agent-skills这种纯库项目,这些问题全是噪音。--no-interactive让它使用所有默认值,瞬间完成创建。后续如果需要添加测试或 lint 规则,直接编辑jest.config.ts.eslintrc.json即可,比交互式选择更可控。

4.2 为技能包添加semantic-release自动化

semantic-release的配置是agent-skills生态的“心脏起搏器”,必须在每个publishable的技能包中正确设置。我们以agent-skills-http为例,详细说明如何一步步配置。

步骤 1:安装依赖

# 在工作区根目录运行 pnpm add -D -w semantic-release @semantic-release/npm @semantic-release/github conventional-changelog-conventionalcommits

-w参数表示安装到整个工作区(workspace),而不是某个特定包。

步骤 2:配置release.config.jslibs/skills/http/目录下创建release.config.js

// libs/skills/http/release.config.js module.exports = { branches: ['main'], plugins: [ // 1. 分析 Git 提交,生成 CHANGELOG ['@semantic-release/commit-analyzer', { preset: 'conventionalcommits', releaseRules: [ { type: 'feat', release: 'minor' }, { type: 'fix', release: 'patch' }, { type: 'perf', release: 'patch' }, { type: 'refactor', release: 'patch' }, { type: 'test', release: 'patch' }, { type: 'chore', release: 'patch' }, { type: 'docs', release: 'patch' }, { scope: 'core', release: 'patch' }, // 如果 core 包有变更,http 包也应 patch ], parserOpts: { noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES'], }, }], // 2. 生成 CHANGELOG.md ['@semantic-release/release-notes-generator', { preset: 'conventionalcommits', presetConfig: { types: [ { type: 'feat', section: 'Features' }, { type: 'fix', section: 'Bug Fixes' }, { type: 'perf', section: 'Performance Improvements' }, { type: 'refactor', section: 'Code Refactoring' }, { type: 'test', section: 'Tests' }, { type: 'chore', section: 'Chores' }, { type: 'docs', section: 'Documentation' }, { type: 'style', section: 'Styles' }, { type: 'build', section: 'Build System' }, { type: 'ci', section: 'Continuous Integration' }, ], }, }], // 3. 发布到 npm registry ['@semantic-release/npm', { npmPublish: true, pkgRoot: 'dist/libs/skills/http', // Nx 构建产物的路径 }], // 4. 创建 GitHub Release ['@semantic-release/github', { assets: ['dist/libs/skills/http/**/*.{js,ts,md}'], message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}', }], ], };

步骤 3:配置 CI(GitHub Actions).github/workflows/release.yml中添加:

name: Release on: push: branches: [main] paths: - 'libs/skills/http/**' - 'libs/core/**' # 添加其他技能包路径 jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: '18' registry-url: 'https://registry.npmjs.org' - name: Install dependencies run: pnpm install - name: Build http skill run: pnpm nx build agent-skills-http - name: Semantic Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release

注意事项:paths过滤是性能关键
on.push.paths中精确指定哪些文件变更会触发 Release,可以避免每次main分支推送都跑一遍 Release 流程。例如,只有libs/skills/http/**下的文件变了,才触发agent-skills-http的发布。这能节省大量 CI 时间和资源。

4.3 开发一个新技能:agent-skills-sms(短信发送)

现在,让我们动手开发一个真实的、可投入生产的技能包:agent-skills-sms。它将封装一个主流短信服务商(如 Twilio 或国内的阿里云短信)的 API 调用。

步骤 1:生成新库

nx g @nrwl/node:library --name=agent-skills-sms --directory=libs/skills/sms --publishable --importPath=@agent-skills/sms --no-interactive

步骤 2:定义核心接口libs/skills/sms/src/lib/sms-skill.interface.ts中:

export interface SmsSkill { /** * 发送单条短信 * @param to 手机号码,E.164 格式(如 '+8613800138000') * @param message 短信内容,最大 70 个字符(中文) * @param options 配置项 * @returns Promise<SmsSendResult> */ send: ( to: string, message: string, options?: { from?: string; // 发送号码或签名 templateId?: string; // 模板 ID,用于模板短信 } ) => Promise<SmsSendResult>; } export interface SmsSendResult { success: boolean; messageId: string; // 服务商返回的唯一消息 ID cost: number; // 本次发送消耗的短信条数(长短信会拆分) rawResponse: any; // 原始响应,用于调试 }

步骤 3:实现核心逻辑(以 Twilio 为例)libs/skills/sms/src/lib/twilio-sms-skill.ts中:

import { SmsSkill, SmsSendResult } from './sms-skill.interface'; import { Twilio } from 'twilio

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询