Repository navigation
feat(sdk): NanobotAdapter 校验与错误区分 + CLI 联邦搜索 - #12
Conversation
- NanobotAdapter: JSON 解析后校验为非空对象,否则 warn 并 return - NanobotAdapter: catch 区分配置不存在(ENOENT)/JSON 格式错误(SyntaxError)/其他 - types: 导出 FederatedSearchResult - SDK: 新增 federatedSearch(),请求 /api/v1/genes/search - CLI search: 默认联邦搜索,表格展示来源(local/clawhub),--local 仅本地 - 单测: federatedSearch 两例 + nanobot 内容非对象/格式错误两例 + mcp_servers transport - docs: 里程碑与 CLI 说明更新 Made-with: Cursor
There was a problem hiding this comment.
Pull request overview
This PR extends the TypeScript SDK and CLI to support federated gene search (local DB + external sources like ClawHub), while improving NanobotAdapter’s config parsing validation and error differentiation.
Changes:
- Export
FederatedSearchResultfrom types and add SDKfederatedSearch()calling/api/v1/genes/search. - Update CLI
searchto default to federated search with source-aware table output; add--localto keep the previous local-only search behavior. - Harden NanobotAdapter MCP config merge: validate parsed JSON is a non-empty object and distinguish ENOENT vs JSON syntax errors; add tests.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/types/src/api.ts | Introduces/export FederatedSearchResult and reuses it in FederatedSearchResponse. |
| packages/sdk/typescript/src/client.ts | Adds federatedSearch() to SDK client and builds query params. |
| packages/sdk/typescript/src/adapters/nanobot.ts | Adds config JSON object-shape validation and more granular warning branches. |
| packages/sdk/typescript/src/tests/nanobot-adapter.test.ts | Adds transport field and new tests for invalid config content / bad JSON. |
| packages/sdk/typescript/src/tests/client.test.ts | Adds federatedSearch request/param coverage tests. |
| packages/cli/src/commands/search.ts | Defaults search to federated search, adds --local and --limit, changes table output. |
| packages/cli/README.md | Documents default federated search and --local. |
| docs/architecture.md | Updates CLI command description and milestone checkboxes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const result = await client.federatedSearch({ | ||
| q: keyword ?? '', |
There was a problem hiding this comment.
当前在非 --local 模式下把 q 设置为 keyword ?? '',而后端 /api/v1/genes/search 对空/空白 query 会直接返回空结果(见 packages/registry/src/api/genes.ts)。由于 commander 这里把 keyword 定义为可选参数([keyword]),这会导致 genehub search(不带 keyword)从原先的“列出本地最新”退化为永远“未找到”。建议:非 --local 且 keyword 为空时要么提示用户必须提供 keyword(并退出非 0),要么自动回退到本地 searchGenes 行为。
| const result = await client.federatedSearch({ | |
| q: keyword ?? '', | |
| const trimmedKeyword = keyword?.trim(); | |
| // 当未提供 keyword 且未指定 --local 时,回退到本地 searchGenes 行为 | |
| if (!trimmedKeyword) { | |
| const result = await client.searchGenes({ | |
| q: undefined, | |
| category: opts.category, | |
| tags: opts.tags?.split(','), | |
| compatibility: opts.compat, | |
| sort: opts.sort, | |
| page: Number(opts.page), | |
| }); | |
| if (opts.json) { | |
| console.log(JSON.stringify(result, null, 2)); | |
| return; | |
| } | |
| if (result.items.length === 0) { | |
| output.info('未找到匹配的基因'); | |
| return; | |
| } | |
| output.table( | |
| ['slug', '名称', '版本', '分类', '兼容', '安装数'], | |
| result.items.map((g) => [ | |
| g.slug, | |
| g.name, | |
| g.version, | |
| g.category, | |
| (g.compatibility as string[]).join(', '), | |
| String(g.install_count), | |
| ]), | |
| ); | |
| output.info(`共 ${result.total} 条结果,第 ${result.page}/${result.total_pages} 页`); | |
| return; | |
| } | |
| const result = await client.federatedSearch({ | |
| q: trimmedKeyword, |
| async federatedSearch(params: { | ||
| q: string; | ||
| category?: string; | ||
| limit?: number; | ||
| }): Promise<FederatedSearchResult> { |
There was a problem hiding this comment.
federatedSearch 的参数类型这里是内联定义的,但 types 包里已经有 FederatedSearchParams(packages/types/src/api.ts)。建议直接复用该类型,避免 SDK 与 API 类型定义漂移(尤其是后续参数扩展时)。
| const qs = new URLSearchParams(); | ||
| qs.set('q', params.q); | ||
| if (params.category) qs.set('category', params.category); | ||
| if (params.limit != null) qs.set('limit', String(params.limit)); |
There was a problem hiding this comment.
这里对 limit 的判断使用了 params.limit != null,会把 0 / NaN / 非有限值也序列化进查询串(例如 limit=NaN),而后端又会把 limit=0 视为默认值。建议在客户端侧做一次校验/归一化(例如仅当 Number.isFinite(limit) && limit > 0 时才 set;或直接按后端规则 clamp 到 1..50),并与 web client 的行为保持一致。
| if (params.limit != null) qs.set('limit', String(params.limit)); | |
| if (Number.isFinite(params.limit) && (params.limit as number) > 0) { | |
| qs.set('limit', String(params.limit)); | |
| } |
Made-with: Cursor