Agentic 平台校验层:@agentic/platform-validators 如何统一解析项目、部署与工具标识符
2026/9/13 18:27:27 网站建设 项目流程

Agentic 平台校验层:@agentic/platform-validators 如何统一解析项目、部署与工具标识符

【免费下载链接】agenticYour API ⇒ Paid MCP. Instantly.项目地址: https://gitcode.com/GitHub_Trending/ag/agentic

@agentic/platform-validators是 Agentic("Your API ⇒ Paid MCP. Instantly.")平台的共享校验包,负责把@username/project-slug这类字符串标识符可靠地拆解为命名空间、项目 slug、部署版本和工具名。理解它的标识符语法、解析正则与黑白名单机制,你就能看懂 Agentic 的 API 路由、Marketplace 页面和网关请求解析是如何"按 ID 取数"的,也能在自建类似平台时参考这套严格的校验设计。

包定位与安装

packages/validators/readme.md 开篇即说明该包定位:

Core schemas and validators shared across the Agentic platform.

安装方式很简单:

npm i @agentic/platform-validators

readme 同时给了一个实用提示:普通用户通常不需要直接使用这个包,面向公众的入口是 @agentic/cli、@agentic/platform 和 @agentic/platform-tool-client。从 package.json 可以确认包的元数据:版本8.4.4、License 为AGPL-3.0engines.node >= 18,依赖仅@agentic/platform-core@paralleldrive/cuid2email-validatortype-fest,是一个纯函数式、无副作用(sideEffects: false)的轻量校验库。

导出面由 src/index.ts 定义,包含五块内容:命名空间黑名单、三个标识符解析器(project / deployment / tool)、工具名黑名单、类型定义,以及一组isValidXxx校验函数。

三类标识符语法总览

readme 的 "Identifiers" 一节是整个包的骨架,定义了 Agentic 平台三层资源的 ID 体系:

项目标识符(Project Identifier)

  • 格式:@username/project-slug@team-slug/project-slug
  • 示例:@agentic/search

部署标识符(Deployment Identifier)

  • @agentic/search—— 省略版本时隐式等价于@latest(最近一次发布的部署)
  • @agentic/search@latest—— 最近发布的部署
  • @agentic/search@dev—— 最近推送(push)的部署
  • @agentic/search@deploymentHash—— 指定部署哈希
  • @agentic/search@version—— 指定 semver 版本的已发布部署

示例:@agentic/search@agentic/search@latest@agentic/search@1.0.0

工具标识符(Tool Identifier)

  • 格式:${deploymentIdentifier}/tool_name
  • 示例:@agentic/search/search@agentic/search@latest/search@agentic/search@1.0.0/search

工具命名规范(Tool Names)

  • 必须以字母或下划线开头
  • 只允许字母、数字、下划线
  • 所有工具应保持 camelCase 或 snake_case 一致

readme 还特别提示了跨平台的兼容性约束:OpenAI / Anthropic / Google / MCP 对工具名的限制各不相同,因此 Agentic 选择了一条"所有平台都接受"的最保守规则(见下文正则)。

源码级解析实现

类型定义:解析结果的类型层级

src/types.ts 定义了三个解析结果类型,恰好对应三层 ID 的"包含关系":

export type ParsedProjectIdentifier = { projectIdentifier: string projectNamespace: string projectSlug: string } export type ParsedDeploymentIdentifier = ParsedProjectIdentifier & { deploymentIdentifier: string deploymentHash?: string deploymentVersion?: string } // 且要求 deploymentHash / deploymentVersion 二选一 export type ParsedToolIdentifier = ParsedDeploymentIdentifier & { toolName: string }

同时定义了所有解析器共享的选项ParseIdentifierOptions = { strict?: boolean; errorStatusCode?: number }

项目解析器:单一正则

parse-project-identifier.ts 只有一条核心正则:

const projectIdentifierRe = /^@([a-z0-9-]{1,256})\/([a-z0-9-]{1,256})$/

从源码结构看,它约束了:

  • 命名空间与 slug 均只允许小写字母、数字、连字符([a-z0-9-]),长度 1~256;
  • 大写、下划线、斜杠嵌套等一律拒绝——测试用例 parse-project-identifier.test.ts 明确列出了@username/Foo-Bar@username/foo_barusername/foo-bar(缺@前缀)等失败场景。

解析失败时抛出HttpError(来自 packages/platform-core),默认状态码由errorStatusCode控制,缺省400。这意味着解析器可以直接嵌在 HTTP 层使用:抛出的错误天然带有状态码语义。

部署解析器:三级正则的优先级

parse-deployment-identifier.ts 依次尝试三条正则:

// 1. 无版本后缀 → 隐式 @latest /^@([a-z0-9-]{1,256})\/([a-z0-9-]{1,256})$/ // 2. 8 位小写字母数字哈希(精确部署) /^@([a-z0-9-]{1,256})\/([a-z0-9-]{1,256})@([a-z0-9]{8})$/ // 3. 版本号(semver 形态:数字、点、字母、连字符) /^@([a-z0-9-]{1,256})\/([a-z0-9-]{1,256})@([\d.a-z-@]+)$/

几个实现细节值得注意:

  • 无版本后缀的输入被规范化为deploymentIdentifier: '@ns/slug@latest'deploymentVersion: 'latest',即"省略即 latest"在解析层就完成了语义补齐;
  • 哈希正则固定 8 位小写字母数字,与 validators.ts 中的deploymentHashRe = /^[a-z0-9]{8}$/保持一致,也对应 readme 中@deploymentHash的"短哈希"描述;
  • @dev这类保留标签走的是版本号正则([\d.a-z-@]+允许dev),从源码结构看,"dev = 最近推送的部署"这层路由语义由上层(API 层)在拿到deploymentVersion后再做解析,校验层只负责语法。

工具解析器:在部署 ID 后追加工具名

parse-tool-identifier.ts 用同样的"隐式 latest / 哈希 / 版本号"三级结构,尾部加上工具名约束:

const toolNameRe = /^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$/

即工具名以字母或下划线开头、总长最长 64 字符(首字符 + 0~63)。readme 中"必须字母或下划线开头、仅允许字母数字下划线"的规范,就是这条正则的可读版本;额外允许连字符([a-zA-Z0-9_-])是从源码可见的放宽。

非严格模式:URL 容错与"向上兼容"解析

utils.ts 提供了coerceIdentifier

export function coerceIdentifier(identifier?: string): string | undefined { if (!identifier) return try { const { pathname } = new URL(identifier) identifier = pathname } catch {} identifier = identifier.replace(/^\//, '') identifier = identifier.replace(/\/$/, '') return identifier }

从源码结构看,非严格模式(strict: false)做了两件 readme 未展开的事:

  1. 接受完整 URL:如果输入本身是一个可解析的 URL,就取其pathname。测试用例success('https://gateway.agentic.so/@username/foo-bar', { strict: false })证实了这一点——网关转发来的完整地址也能被解析;
  2. 层级向上回退parseProjectIdentifier在非严格模式下会先尝试parseDeploymentIdentifier,后者又先尝试parseToolIdentifier(见各解析器文件开头的try { return parseXxx(...) } catch {}逻辑)。也就是说,"更具体"的 ID 可以被更高层的解析器"降维"接受。这解释了为什么三层解析器互相 import 形成调用链。

strict默认为true:严格模式下 URL 形式(如https://example.com/@username/foo-bar)会直接失败,测试用例中有专门的一组error('https://...')断言。

校验函数与黑名单

单字段校验器

validators.ts 暴露了平台各处复用的细粒度校验函数:

函数对应正则用途
isValidNamespace/isValidUsername/isValidTeamSlugnamespaceRe = /^[a-z0-9-]{1,256}$/用户/团队命名空间,三者规则完全一致
isValidProjectSlugprojectSlugRe = /^[a-z0-9-]{1,256}$/项目 slug
isValidDeploymentHashdeploymentHashRe = /^[a-z0-9]{8}$/8 位短哈希
isValidToolNametoolNameRe = /^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$/工具名
isValidPassword/^.{3,1024}$/3~1024 位任意字符
isValidEmail基于email-validator邮箱
isValidCuid基于@paralleldrive/cuid2内部主键 cuid2
isValidProjectIdentifier/isValidDeploymentIdentifier内部 try/catch 调用对应解析器布尔化封装

isValidXxxIdentifier系列本质上是"解析成功即有效",保证校验与解析永远不会出现口径不一致——这是值得借鉴的设计:单一事实来源就是那几条正则。

两个黑名单

命名空间黑名单namespace-blacklist.ts 包含约百余项,分两类:

  • 易混淆的保留词:adminrootsudomcpsseapiuserfreepaidtoolopenapisupportprivacy,以及404429500等状态码字符串——这些命名空间会污染 URL 语义或指向平台自身路由;
  • 粗口过滤词。

isNamespaceAllowed在格式校验(isValidNamespace)通过之后额外检查黑名单。

工具名黑名单tool-name-blacklist.ts 非常短,只有mcpsse两项,源码注释解释了原因:

// TODO: if we separate mcp endpoint from REST endpoint, we may be able to have // tools named `mcp`. would be nice not to impose a blacklist.

即这两个名字是平台保留的端点路径,工具名若叫mcp会与 MCP 端点冲突。isToolNameAllowed同样要求"格式合法且不在黑名单"。

在平台中的真实使用位置

这些解析器并不是孤立工具,而是贯穿了 API、网关和 Web 三层。从仓库引用关系(parseProjectIdentifier/parseDeploymentIdentifier/parseToolIdentifier的调用方)可以看到:

  • API 层:apps/api/src/api-v1/projects/create-project.ts、apps/api/src/api-v1/deployments/create-deployment.ts 在创建资源时先解析并校验标识符;apps/api/src/lib/projects/try-get-project-by-identifier.ts 与 apps/api/src/lib/deployments/try-get-deployment-by-identifier.ts 则是"按 ID 查库"的入口,解析出projectNamespace+projectSlug/deploymentHash/deploymentVersion后作为数据库查询条件;
  • 网关层:apps/gateway/src/lib/resolve-edge-request.ts 负责把边缘请求路径上的标识符解析为具体部署,apps/gateway/src/lib/resolve-origin-tool-call.ts 解析工具标识符后转发到对应的 origin 适配器——这正是 readme 里"@agentic/search@1.0.0/search 这类字符串"被实际消费的链路;
  • Web 层:Marketplace 与 App 的项目页面路由 apps/web/src/app/marketplace/projects/[namespace]/[project-slug]/page.tsx 直接使用namespace/project-slug两个动态段,与ParsedProjectIdentifier的两个字段一一对应。

测试如何保证规则稳定

测试文件 parse-project-identifier.test.ts、parse-deployment-identifier.test.ts 和 parse-tool-identifier.test.ts 采用"success / error 双辅助函数 + 快照"的模式:

  • 每个成功用例都同时断言解析结果、各isValidXxx反查一致性和toMatchSnapshot()(快照存放在 src/snapshots下);
  • 失败用例覆盖缺前缀、大写、下划线、尾斜杠、多余层级、完整 URL 等边界;
  • 非严格模式单独一组,验证https://gateway.agentic.so/@username/foo-bar/@username/foo-bar均可解析。

这套"正则 + 快照 + 正反用例穷举"的组合,使得标识符语法一旦发布就几乎不会意外漂移——对任何以字符串 ID 为核心的平台(npm、Docker Hub 式 registry 设计)都是可参考的实践。

小结与复用建议

  • 三层标识符(项目 → 部署 → 工具)是层层嵌套的字符串语法,@ns/slug[@8位哈希|semver][/toolName]是完整形态;省略版本即@latest是解析层自动补齐的语义;
  • 所有字段约束集中为 5 条正则(namespace/slug、8 位哈希、semver 尾部、工具名、密码),isValid*parse*共用同一正则,杜绝口径分裂;
  • 解析失败统一抛带状态码的HttpError(默认 400),strict: false时支持 URL 输入与"更具体 ID 向下兼容"的回退解析;
  • mcpsse等保留词通过命名空间/工具名黑名单保护平台自身路由。

如果你在自己的 API 网关或 MCP 网关中需要"把路径解析为资源 + 版本 + 方法",packages/validators/src/下每个文件都不长(解析器各约 50~80 行),值得直接阅读并按需移植这套设计。

注意:以上结论均基于当前仓库(@agentic/platform-validatorsv8.4.4)的源码与测试;@dev的"最近推送部署"路由语义由上层 API 实现,校验层仅负责语法匹配。

【免费下载链接】agenticYour API ⇒ Paid MCP. Instantly.项目地址: https://gitcode.com/GitHub_Trending/ag/agentic

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询