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-validatorsreadme 同时给了一个实用提示:普通用户通常不需要直接使用这个包,面向公众的入口是 @agentic/cli、@agentic/platform 和 @agentic/platform-tool-client。从 package.json 可以确认包的元数据:版本8.4.4、License 为AGPL-3.0、engines.node >= 18,依赖仅@agentic/platform-core、@paralleldrive/cuid2、email-validator与type-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_bar、username/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 未展开的事:
- 接受完整 URL:如果输入本身是一个可解析的 URL,就取其
pathname。测试用例success('https://gateway.agentic.so/@username/foo-bar', { strict: false })证实了这一点——网关转发来的完整地址也能被解析; - 层级向上回退:
parseProjectIdentifier在非严格模式下会先尝试parseDeploymentIdentifier,后者又先尝试parseToolIdentifier(见各解析器文件开头的try { return parseXxx(...) } catch {}逻辑)。也就是说,"更具体"的 ID 可以被更高层的解析器"降维"接受。这解释了为什么三层解析器互相 import 形成调用链。
strict默认为true:严格模式下 URL 形式(如https://example.com/@username/foo-bar)会直接失败,测试用例中有专门的一组error('https://...')断言。
校验函数与黑名单
单字段校验器
validators.ts 暴露了平台各处复用的细粒度校验函数:
| 函数 | 对应正则 | 用途 |
|---|---|---|
isValidNamespace/isValidUsername/isValidTeamSlug | namespaceRe = /^[a-z0-9-]{1,256}$/ | 用户/团队命名空间,三者规则完全一致 |
isValidProjectSlug | projectSlugRe = /^[a-z0-9-]{1,256}$/ | 项目 slug |
isValidDeploymentHash | deploymentHashRe = /^[a-z0-9]{8}$/ | 8 位短哈希 |
isValidToolName | toolNameRe = /^[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 包含约百余项,分两类:
- 易混淆的保留词:
admin、root、sudo、mcp、sse、api、user、free、paid、tool、openapi、support、privacy,以及404、429、500等状态码字符串——这些命名空间会污染 URL 语义或指向平台自身路由; - 粗口过滤词。
isNamespaceAllowed在格式校验(isValidNamespace)通过之后额外检查黑名单。
工具名黑名单tool-name-blacklist.ts 非常短,只有mcp和sse两项,源码注释解释了原因:
// 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 向下兼容"的回退解析; mcp、sse等保留词通过命名空间/工具名黑名单保护平台自身路由。
如果你在自己的 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),仅供参考