@lark-apaas/coding-steering 0.1.58 → 0.1.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +15 -13
- package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +2 -2
- package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +20 -5
- package/steering/nestjs-react-fullstack/skills/contacts-service/SKILL.md +46 -32
- package/steering/nestjs-react-fullstack/skills/design-guide/SKILL.md +0 -3
- package/steering/nestjs-react-fullstack/skills/design-guide/references/token-mapping.md +3 -3
- package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +28 -59
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +16 -4
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +75 -14
- package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +178 -168
- package/steering/nestjs-react-fullstack/skills_common/mcp-guide/SKILL.md +78 -0
- package/steering/nestjs-react-fullstack/skills_common/mcp-guide/assets/eslint.mcp-ui.config.cjs +19 -0
- package/steering/nestjs-react-fullstack/skills_common/mcp-guide/assets/ui-tsconfig.json +19 -0
- package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/client-onboarding.md +82 -0
- package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/debugging.md +81 -0
- package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/mcp-apps.md +197 -0
- package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/tool-authoring.md +249 -0
- package/steering/nestjs-react-fullstack/skills_common/server-contacts-contract/SKILL.md +19 -0
- package/steering/nestjs-react-fullstack/skills_common/user-identity/SKILL.md +5 -1
- package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +2 -2
- package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +18 -3
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +3 -3
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/references/plugin-coding-guide.md +93 -2
- package/steering/vite-react/skills/plugin-guide/SKILL.md +2 -0
- package/steering/nestjs-react-fullstack/skills/design-guide/references/corporate-blueprint.md +0 -252
package/package.json
CHANGED
package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: client-builtins-file-storage-service
|
|
3
|
-
description: 前端文件存储服务指南,基于 dataloom.storage
|
|
3
|
+
description: 前端文件存储服务指南,基于 dataloom.storage 上传、删除、列表查询和获取文件 URL,包含 uploadFile、remove、list、getDefaultBucketId、generateDownloadUrlFromFilePath。Use when 需要:(1) 上传文件/图片/附件到云存储,(2) 删除存储桶文件,(3) 获取文件列表或浏览目录,(4) 前端把文件信息存入数据库:普通 text/varchar/JSON 字段默认保存 download_url;仅明确使用 file_attachment 类型字段时保存 bucket_id + file_path,(5) file_attachment 字段需用 file_path 换展示 URL,或其他前端文件存储开发。
|
|
4
4
|
steering: true
|
|
5
5
|
steering-topic: client_builtins_file_storage_service
|
|
6
6
|
match-template-name: nestjs-react-fullstack
|
|
@@ -8,11 +8,13 @@ match-template-name: nestjs-react-fullstack
|
|
|
8
8
|
|
|
9
9
|
# 前端文件开发规范
|
|
10
10
|
|
|
11
|
-
> **术语**:下文的 `file_attachment`、`text`
|
|
11
|
+
> **术语**:下文的 `file_attachment`、`text`、`varchar`、`JSON` 指数据库字段的**类型**,不是字段名。
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
13
|
+
- **默认保存展示 URL**:上传后取 `data.download_url`,存到普通 `text` / `varchar` / `JSON` 业务字段;渲染 / 下载时直接作为 `src` / `href`。
|
|
14
|
+
- **后台留路径时仍返回 URL**:后端异步处理可保存 `file_path` 作内部定位;列表 / 详情接口仍必须另返 `download_url` / `downloadURL` 展示 URL,前端只消费该 URL。
|
|
15
|
+
- **仅 `file_attachment` 字段从 file_path 换 URL**:业务明确使用 `file_attachment` 类型字段时,该字段存 `bucket_id` + `file_path`(不能存 `download_url`);前端才用 dataloom SDK 的 `generateDownloadUrlFromFilePath(file_path)`,且 `file_path` 必须来自该字段。
|
|
16
|
+
- **禁止手拼运行态前缀**:不要把 `/runtime/api/v1/storage/object`、`/app/{appId}/runtime`、`/spark/app/{appId}/runtime`、`__runtime__` 当作 URL 合同拼接;使用上传返回值、后端返回值或 SDK 方法原始结果。
|
|
17
|
+
- 上传后的文件还要给后端 AI capability 解析/理解时,前端展示仍用 `download_url`;提交给后端时优先保留 `file_path`(以及可用的 `bucket_id`)。旧记录只有标准 storage `download_url` 时,后端必须先从中解析 `bucket_id + file_path`,再用 `fileService.from(bucketId).createSignedUrl(filePath, 3600)` 生成临时 http(s) URL;不得把 `download_url` 直接交给默认 bucket 的 `FileService.createSignedUrl()`。前端不要构造 signed URL;普通展示/下载仍默认用 `download_url`。
|
|
16
18
|
|
|
17
19
|
# dataloom SDK 文件服务
|
|
18
20
|
|
|
@@ -23,11 +25,11 @@ match-template-name: nestjs-react-fullstack
|
|
|
23
25
|
- **入口边界**:本 SDK 是**应用前端代码**读写应用存储的唯一入口。Agent 自己在对话 / 开发中上传、灌数据或调试文件,先加载应用文件存储操作 skill(按「应用文件存储 / 文件上传 / 文件下载」召回),用其 CLI 命令操作,具体命令一律以该 skill 为准——二者操作同一个应用存储桶,但**该 CLI 命令只供 Agent 在沙箱终端使用,禁止写进页面代码**,页面里一律用本 SDK。
|
|
24
26
|
- @lark-apaas/client-toolkit/dataloom 这个SDK只适用于前端调用,禁止在服务端调用
|
|
25
27
|
- **沙箱 dev 限制**:`uploadFile` 打的 `/app/<appId>/__runtime__/api/v1/storage/object/<bucket>/pre_upload` **在沙箱 dev 下恒 404**,只有发布态可用。这是环境限制不是代码缺陷,按本 skill 写法即为正确,不要为它改代码;开发期要验证上传链路改走接口测试直接打后端接口,或复用库里已有的文件 URL。详见 coding-guide「沙箱 dev 不提供平台 runtime 接口」。
|
|
26
|
-
-
|
|
28
|
+
- 上传成功后,普通 `text` / `varchar` / `JSON` 业务字段默认保存 `data.download_url`,并把它作为页面展示 / 下载 URL
|
|
27
29
|
- **⚠️ 场景区分(重要)**:`dataloom.storage` 仅适用于需要持久化存储文件或获取 `download_url` 保存到数据库的场景。如果文件仅作为插件输入(传给 `capabilityClient`),**必须直接传 File/Blob 对象,禁止先走 dataloom 上传再传 URL**;插件调用(capability)不属于 dataloom,详见 plugin-guide
|
|
28
30
|
- **Server 侧 AI capability 边界**:文件已上传并持久化后,若由后端调用 `CapabilityService` 做文档解析/图片理解,不要把前端展示用 `download_url` 当插件入参;把 `file_path` 和 `bucket_id` 传给后端,由后端 `FileService` 签成临时 http(s) URL。
|
|
29
|
-
- **download_url 格式说明**:`download_url`
|
|
30
|
-
- **文件URL
|
|
31
|
+
- **download_url 格式说明**:`download_url` 可能是相对路径。**禁止**拼接 `window.location.origin` 或任何运行态前缀;平台会自动解析相对路径,直接使用原始值。
|
|
32
|
+
- **文件URL**:调用 `generateDownloadUrlFromFilePath` 时,`file_path` 必须来自数据库 `file_attachment` 类型字段,禁止拼接;普通业务字段已有 `download_url` 时不要调用本方法。
|
|
31
33
|
|
|
32
34
|
## bucketId
|
|
33
35
|
关于bucketId的获取,在代码工程中已经预置了一个获取bucketid的方法,路径为`@lark-apaas/client-toolkit/tools/storage`中,直接具名导入即可使用。
|
|
@@ -83,8 +85,8 @@ type StorageError = DataLoomError | StorageError | StorageUnknownError;
|
|
|
83
85
|
| 字段名 | 类型 | 说明 |
|
|
84
86
|
|--------|------|---------|
|
|
85
87
|
| `data.bucket_id` | `string` | 所属bucket ID |
|
|
86
|
-
| `data.file_path` | `string` |
|
|
87
|
-
| `data.download_url` | `string` | 文件
|
|
88
|
+
| `data.file_path` | `string` | 文件路径,不是文件 URL;只写入 `file_attachment` 类型字段,或作为后端内部处理定位字段 |
|
|
89
|
+
| `data.download_url` | `string` | 文件 URL(可能是相对路径),普通字段默认保存它,可直接用于下载文件或渲染图片;**禁止**拼接域名前缀,见「使用注意」 |
|
|
88
90
|
| `error` | `StorageError \| null` | 错误信息,成功时为null |
|
|
89
91
|
|
|
90
92
|
##### 使用示例
|
|
@@ -192,13 +194,13 @@ const { data, error } = await dataloom
|
|
|
192
194
|
|
|
193
195
|
#### 4. 根据filePath生成downloadUrl接口 (generateDownloadUrlFromFilePath)
|
|
194
196
|
|
|
195
|
-
**仅用于 `file_attachment`
|
|
197
|
+
**仅用于 `file_attachment` 类型字段场景**:只有手头是从该字段读出的 `file_path` 时,才用它换可渲染 / 下载 URL。普通 `text` / `varchar` / `JSON` 字段上传文件回显,默认直接用已保存或后端返回的 `download_url` / `downloadURL`,禁止调用本接口。
|
|
196
198
|
|
|
197
199
|
##### 入参说明
|
|
198
200
|
|
|
199
201
|
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|
|
200
202
|
|--------|--------|---------|--------|---------|
|
|
201
|
-
| `filePath` | `string` | ✅ | - | 文件的 filePath
|
|
203
|
+
| `filePath` | `string` | ✅ | - | 文件的 filePath。**只能从数据库 `file_attachment` 类型字段读取,不得从普通业务字段取值或拼接路径** |
|
|
202
204
|
|
|
203
205
|
##### 出参说明
|
|
204
206
|
| 类型 | 说明 |
|
|
@@ -325,7 +327,7 @@ export default FileUploadDemo;
|
|
|
325
327
|
|
|
326
328
|
| 要点 | 说明 |
|
|
327
329
|
|------|------|
|
|
328
|
-
| 上传流程 | 展示/下载:`uploadFile(file)` → 取 `data.download_url` → POST
|
|
330
|
+
| 上传流程 | 展示/下载:`uploadFile(file)` → 取 `data.download_url` → POST 后端保存为展示 URL;后端 AI 解析:同时保存/提交 `data.file_path` 与 `data.bucket_id` 供后端签名 |
|
|
329
331
|
| 文件对象 | 直接传原始 `File`,**禁止**包装或修改文件名 |
|
|
330
332
|
| 组件依赖 | shadcn/ui `Button`/`Card` + `lucide-react` + `sonner` |
|
|
331
333
|
| 请求实例 | 必须用 `axiosForBackend`,禁止 `fetch` |
|
|
@@ -319,7 +319,7 @@ useEffect 无限循环、依赖数组管理、useMemo/useCallback 记忆化等
|
|
|
319
319
|
| 场景 | 做法 | 注意事项 |
|
|
320
320
|
|------|------|----------|
|
|
321
321
|
| 用户反馈 | 使用 `toast` (sonner) 显示友好消息 | 消息简洁、可操作,避免暴露技术细节 |
|
|
322
|
-
| 前端日志 | 使用 `logger` (`@lark-apaas/client-toolkit/logger`) | 禁止 console
|
|
323
|
-
| 后端日志 | 使用 `@nestjs/common` 的 Logger | 禁止 console
|
|
322
|
+
| 前端日志 | 使用 `logger` (`@lark-apaas/client-toolkit/logger`) | 禁止 console;签名见 `coding-guide`「日志约定」,可多参,`logger.log` 只接 `{ level, args }` |
|
|
323
|
+
| 后端日志 | 使用 `@nestjs/common` 的 Logger | 禁止 console;签名见 `coding-guide`「日志约定」,可多参,无 `info` 方法 |
|
|
324
324
|
| 业务错误 | 区分预期错误与意外错误 | 预期错误用 `logger.warn`,意外错误用 `logger.error` |
|
|
325
325
|
| 异常处理 | 禁止静默处理异常 | 必须显示明确的错误信息,参考 `coding-guide` 相关规范 |
|
|
@@ -284,9 +284,24 @@ dev server 只代理 `/api`、`/openapi`、`/__innerapi__`(外加 legacy 的 `
|
|
|
284
284
|
|
|
285
285
|
## 日志约定
|
|
286
286
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
-
|
|
287
|
+
前后端是两个独立的 logger,签名不同,不要互相套用。
|
|
288
|
+
|
|
289
|
+
**前端** `@lark-apaas/client-toolkit/logger`:同一个 `logger` 对象,六个方法分两类签名。
|
|
290
|
+
|
|
291
|
+
- `debug` / `info` / `warn` / `error` / `success`: `(message: any, ...args: any[])`
|
|
292
|
+
级别已在方法名里;可多参,`message` 不限 string。
|
|
293
|
+
例:`logger.error('创建失败', error)`
|
|
294
|
+
- `log`: `({ level, args, meta? }: LogWithMeta)`
|
|
295
|
+
级别不在方法名里必须显式传;只接对象,传裸字符串会 TS2345。
|
|
296
|
+
例:`logger.log({ level: 'info', args: ['创建成功', id] })`
|
|
297
|
+
|
|
298
|
+
**后端** `@nestjs/common` 的 `Logger`:
|
|
299
|
+
|
|
300
|
+
- 没有 `info` 方法,用 `logger.log` 代替;有 `debug` / `verbose` / `fatal`
|
|
301
|
+
- `(message: any, ...optionalParams)`,可多参
|
|
302
|
+
- 最后一个 string 参数会被当作 `context`(`error` 是 `stack` + `context`),传对象前自己 `JSON.stringify`
|
|
303
|
+
|
|
304
|
+
两端共同:禁止 `console`;错误日志输出完整堆栈。
|
|
290
305
|
|
|
291
306
|
## Database
|
|
292
307
|
|
|
@@ -511,7 +526,7 @@ await db.select().from(users).where(eq(users.adminUser, userId));
|
|
|
511
526
|
async submitPublic(@Body() dto) { ... }
|
|
512
527
|
```
|
|
513
528
|
8. **OpenAPI 文档同步**:改动 `*.openapi.controller.ts` 或其引用的 interface / service 返回值 / schema 字段时,加载 `openapi-guide` skill,同步更新 `docs/openapi.json`
|
|
514
|
-
9. **服务端查用户信息(CRITICAL
|
|
529
|
+
9. **服务端查用户信息(CRITICAL)**:先区分当前用户 `userId`、应用 `appId` 与人员 ID,再确认请求上下文是否已有非空 `appId`,最后选择查询能力和响应字段语义。仅有 `userId` 或给定人员 ID 都不能推导出 `appId`;只有已具备非空 `appId` 时才用 `AuthNPaasService.listUsersByIds`,并保留批量字段映射、平台错误与查无此人的区别。匿名入口无法证明该前置条件时,主体读取保持独立,返回已有稳定 ID,展示名 / 头像建模为可空或可选;明确要求匿名真实姓名但无受支持 `appId` 来源时报告能力阻塞。禁止凭空补身份头、裸调平台、持久化姓名副本、service 广域 `try/catch` 或固定“未知用户”降级。动手前加载 `contacts-service` skill 第六节
|
|
515
530
|
|
|
516
531
|
## 异常处理
|
|
517
532
|
|
|
@@ -826,7 +841,7 @@ return <h1>{data?.title || '未知标题'}</h1>;
|
|
|
826
841
|
|
|
827
842
|
应用部署在 `/app/<appId>/` 路径前缀下,组件内**禁止用根绝对路径**引用静态资产(如 `src="/x.svg"`、`src="/bg.mp4"`)——根绝对路径脱离前缀,预览与线上必 404。
|
|
828
843
|
|
|
829
|
-
- 用户上传的媒体要在页面展示 →
|
|
844
|
+
- 用户上传的媒体要在页面展示 → 默认用上传返回或后端返回的 `download_url` / `downloadURL`;仅明确使用 `file_attachment` 类型字段时,才按 client-builtins-file-storage-service 用 `generateDownloadUrlFromFilePath` 从该字段的 `file_path` 换 URL。不要下载进 `public/` 再硬编码路径。
|
|
830
845
|
- 项目自带的静态资产 → 放 `client/src/assets/` 用 ES import(`import logoUrl from '@/assets/x.svg'`),构建管线自动带前缀;新增 `.svg`/`.mp4` 等类型须在 `client/src/types/global.d.ts` 补 `declare module` 声明。
|
|
831
846
|
|
|
832
847
|
### 排查资源 404
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: contacts-service
|
|
3
|
-
description: "
|
|
3
|
+
description: "服务端按 ID 批量查用户(AuthNPaasService.listUsersByIds,返回项只有 miaodaUserID / name / avatar,没有 userId / userName)+ 前端搜人选人。Use when 在产物服务端 / OpenAPI 接口里按 ID 查用户姓名头像、搜人/选人/人员选择器/部门选择器/群组选择、获取或展示用户与部门信息、把用户/部门/群组 ID 传给飞书内置插件或飞书开放平台 API、人员字段入库或导出。统一妙搭 ID 体系(miaoda_user_id / employee_id / open_department_id / open_chat_id;lark_* 内部 ID 禁用)、字段获取分级与权限引导、服务端可用与不可用的通讯录能力边界。触发词:AuthNPaasService, listUsersByIds, 服务端查用户, 后端查用户, 批量查用户, 按ID查人, 查用户姓名, 查用户头像, MiaodaUserInfo, 搜人, 选人, 人员选择器, 部门选择器, 群组选择, UserSelect, DepartmentSelect, ChatSelect, 获取用户信息, 用户字段, 部门信息, 群组信息, employee_id, open_id, union_id, open_department_id, open_chat_id, miaoda_user_id, lark_id, 飞书用户ID, 飞书部门ID, 外部用户, 工号, 手机号, 直属上级, 人员入库, 人员导出, id_convert, 通讯录, 选负责人, 选审批人, 传插件, 传开放平台, 选中的人传给, 把选中的人, OpenAPI 查用户"
|
|
4
4
|
steering: true
|
|
5
5
|
steering-topic: contacts_service
|
|
6
6
|
match-template-name: nestjs-react-fullstack
|
|
@@ -14,6 +14,10 @@ match-template-name: nestjs-react-fullstack
|
|
|
14
14
|
>
|
|
15
15
|
> **⚠️ 先分流**:一~五节讲的是**浏览器里**的搜人 / 选择器口径。代码跑在**产物服务端**(NestJS service / controller,尤其 `*.openapi.controller.ts`)时,能力边界完全不同——直接看[第六节](#六服务端--openapi-态怎么查通讯录)。
|
|
16
16
|
|
|
17
|
+
> `AuthNPaasService` 分流:`getCurrentUserLarkUserId`、`getBatchLarkUserIds`、`getBatchMiaodaUserIds` 属于 ID 转换,见 [`user-identity`](../user-identity/SKILL.md) 第三节;`listUsersByIds` 属于按妙搭 userId 查他人姓名 / 头像,见本 skill 第六节。
|
|
18
|
+
>
|
|
19
|
+
> `listUsersByIds` 返回 `(MiaodaUserInfo | null)[]`,与入参等长同序;单项只有 `miaodaUserID` / `name`(I18nText)/ `avatar`,没有 `userId` / `userName`。字段以第六节或 `node_modules/@lark-apaas/nestjs-authnpaas/dist/index.d.ts` 为准,禁止按常见 SDK 命名猜字段。
|
|
20
|
+
|
|
17
21
|
## 命名约定(必读)
|
|
18
22
|
|
|
19
23
|
**以服务端字段为准。** 同一个 ID 在不同层有不同书写:
|
|
@@ -202,15 +206,19 @@ match-template-name: nestjs-react-fullstack
|
|
|
202
206
|
|
|
203
207
|
## 六、服务端 / OpenAPI 态怎么查通讯录
|
|
204
208
|
|
|
205
|
-
### 6.1
|
|
209
|
+
### 6.1 先确认两类上下文
|
|
206
210
|
|
|
207
|
-
|
|
211
|
+
`userId` 标识当前用户,`appId` 标识当前应用,两者不能互相替代。`AuthNPaasService.listUsersByIds` 不要求当前 `userId`,但要求请求上下文已有**非空 `appId`**;已有 `miaoda_user_id` 不能证明或推导出该前置条件。匿名 / OpenAPI 请求没有登录用户,也不能默认具有 `appId`。
|
|
208
212
|
|
|
209
|
-
|
|
213
|
+
按以下顺序决策:
|
|
210
214
|
|
|
211
|
-
|
|
215
|
+
1. 区分业务数据中的人员 ID、当前 `userId` 与请求上下文 `appId`。
|
|
216
|
+
2. 确认已支持的运行时机制是否为当前入口提供非空 `appId`,不得凭空补身份头。
|
|
217
|
+
3. 再选择查询能力并定义响应字段语义。
|
|
212
218
|
|
|
213
|
-
|
|
219
|
+
### 6.2 有非空 appId 时按 ID 批量查人
|
|
220
|
+
|
|
221
|
+
请求上下文有非空 `appId` 时,从项目统一入口 `@lark-apaas/fullstack-nestjs-core` 取 `AuthNPaasService` 并调用 `listUsersByIds`。`PlatformModule.forRoot()` 已注册 global 的 `AuthNPaasModule`,不要在业务 module 加 `providers` 或增加直接依赖:
|
|
214
222
|
|
|
215
223
|
```ts
|
|
216
224
|
import { AuthNPaasService } from '@lark-apaas/fullstack-nestjs-core';
|
|
@@ -220,44 +228,50 @@ export class TicketService {
|
|
|
220
228
|
constructor(private readonly authn: AuthNPaasService) {}
|
|
221
229
|
|
|
222
230
|
async attachAssignees(ids: string[]) {
|
|
223
|
-
// 返回与入参等长同序,未命中的位置是 null
|
|
224
231
|
const users = await this.authn.listUsersByIds(ids);
|
|
225
|
-
return users.map((
|
|
226
|
-
miaoda_user_id: ids[
|
|
227
|
-
name:
|
|
228
|
-
avatar:
|
|
232
|
+
return users.map((user, index) => ({
|
|
233
|
+
miaoda_user_id: ids[index],
|
|
234
|
+
name: user?.name?.zh_cn ?? user?.name?.en_us,
|
|
235
|
+
avatar: user?.avatar?.image?.large,
|
|
229
236
|
}));
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
```
|
|
233
240
|
|
|
234
|
-
- 入参是 **miaoda_user_id**,单次最多 **100** 个;返回 `(MiaodaUserInfo | null)[]
|
|
235
|
-
- 只有 `miaodaUserID` / `name` / `avatar`
|
|
236
|
-
-
|
|
237
|
-
-
|
|
241
|
+
- 入参是 **miaoda_user_id**,单次最多 **100** 个;返回 `(MiaodaUserInfo | null)[]`,与入参等长同序,未命中的位置是 `null`
|
|
242
|
+
- 只有 `miaodaUserID` / `name` / `avatar` 三个字段;`name` 是 `I18nText`,按语言字段映射
|
|
243
|
+
- 平台错误抛 `HttpException`(502),不要吞掉;它与返回 `null` 的查无此人语义不同
|
|
244
|
+
- `import` 不到说明 core 版本未提供该能力:报告阻塞,不要增加直接依赖或自实现替代品
|
|
238
245
|
|
|
239
|
-
|
|
246
|
+
登录态内部 `/api` 请求若具有非空 `appId`,保留上述合法批量查询;是否有 `userId` 不是这项能力的判据。
|
|
240
247
|
|
|
241
|
-
### 6.3
|
|
248
|
+
### 6.3 无法保证 appId 的匿名入口
|
|
242
249
|
|
|
243
|
-
|
|
244
|
-
|---|:---:|---|
|
|
245
|
-
| 按 ID 批量查人 | ✅ | `AuthNPaasService.listUsersByIds` |
|
|
246
|
-
| 妙搭 ↔ 飞书 ID 转换 | ✅ | `AuthNPaasService.getBatchLarkUserIds` / `getBatchMiaodaUserIds` |
|
|
247
|
-
| 搜人 / 搜部门 / 搜群(按关键词) | ❌ | 飞书开放平台通讯录 API(应用身份 + 显式授权的通讯录范围) |
|
|
248
|
-
| 取手机号 / 职位 / 工号 / 直属上级 | ❌ | 同上,字段 ↔ 权限映射见 [`feishu` › contacts.md](../../../feishu/references/contacts.md) |
|
|
249
|
-
| 知道「当前调用者是谁」 | ❌ | OpenAPI 语义上没有当前用户;内部 `/api` 路由才有 `req.userContext.userId` |
|
|
250
|
-
| 按 ID 批量查群 | ❌ | 暂无服务端能力;群信息在前端取,或走开放平台 IM API |
|
|
250
|
+
匿名 / OpenAPI 入口无法证明非空 `appId` 时,主体资源读取必须独立于人员查询:
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
- 返回业务数据中已有的稳定 `miaoda_user_id`,不要因展示名查询失败而让主体读取失败
|
|
253
|
+
- 在 `shared/api.interface.ts` 和 `docs/openapi.json` 中把 `name`、`avatar` 等展示值建模为可空或可选;不要用固定“未知用户”或空字符串伪装已取得展示值
|
|
254
|
+
- 用户明确要求匿名响应必须返回真实姓名、但当前没有受支持的 `appId` 来源时,报告运行时能力阻塞;不要把未完成实现当作成功
|
|
253
255
|
|
|
254
|
-
### 6.4
|
|
256
|
+
### 6.4 能力边界与禁止项
|
|
255
257
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
| 想做的事 | 条件 / 路径 |
|
|
259
|
+
|---|---|
|
|
260
|
+
| 按 miaoda_user_id 批量查人 | 请求上下文有非空 `appId` → `AuthNPaasService.listUsersByIds` |
|
|
261
|
+
| 妙搭 ↔ 飞书 ID 转换 | `AuthNPaasService.getBatchLarkUserIds` / `getBatchMiaodaUserIds`;不能用来判断外部用户是否存在 |
|
|
262
|
+
| 搜人 / 搜部门 / 搜群 | 飞书开放平台通讯录 API(应用身份 + 显式授权的通讯录范围) |
|
|
263
|
+
| 取手机号 / 职位 / 工号 / 直属上级 | 同上,字段 ↔ 权限映射见 [`feishu` › contacts.md](../../../feishu/references/contacts.md) |
|
|
264
|
+
| 知道当前调用者是谁 | 登录态内部 `/api` 才读取 `req.userContext.userId` |
|
|
265
|
+
| 按 ID 批量查群 | 暂无服务端能力;在前端获取或使用开放平台 IM API |
|
|
266
|
+
|
|
267
|
+
以下做法不能作为匿名人员展示的替代方案:
|
|
268
|
+
|
|
269
|
+
1. 凭空补身份头或把人员 ID 当作 `appId`
|
|
270
|
+
2. 复用依赖登录态的用户资料 service,或自己写 HTTP 裸调平台通讯录接口
|
|
271
|
+
3. 在数据库持久化姓名 / 头像副本再 join 返回;通讯录展示值会过期且受权限约束
|
|
272
|
+
4. 用 service 广域 `try/catch` 吞掉人员查询错误,或固定返回“未知用户”
|
|
273
|
+
5. 用 `getCurrentUserLarkUserId()` 查目标用户,或用 `getBatchLarkUserIds()` 判断用户是否存在
|
|
274
|
+
6. 把 `name` 当字符串直接渲染或入库
|
|
261
275
|
|
|
262
276
|
---
|
|
263
277
|
|
|
@@ -152,9 +152,6 @@ frontend-design 自定义方向没有可复制的源文件,只把基础设计
|
|
|
152
152
|
- `metabase` **湖光蓝调**
|
|
153
153
|
视觉:纯白与极浅蓝平面,深蓝墨字,明亮蓝色用于主按钮和交互焦点 / Lato 人文无衬线字体,粗体标题与常规正文形成清晰层级 / 小到中圆角,1px 半透明中性边框,平铺卡片轻投影,浮层采用更深漫射阴影 / 浅蓝静区、实底蓝主按钮与描边次按钮 / 通透、清爽、平和
|
|
154
154
|
|
|
155
|
-
- `corporate-blueprint` **蓝图**
|
|
156
|
-
视觉:冷浅灰画布、白色卡片、深蓝主强调,图表以蓝色深浅层级为主,状态标签保留语义色 / 无衬线大标题与小号全大写宽字距标签,数值列等宽对齐 / 主卡片零圆角、顶部 3px 深蓝边线,配轻阴影与细分隔线 / 深蓝渐变头部、斜切几何装饰与编号章节 / 秩序、严谨、精确
|
|
157
|
-
|
|
158
155
|
- `pm-spec` **规格蓝本**
|
|
159
156
|
视觉:冷灰蓝底、白色纸面与深墨文字,靛紫用于小面积结构性强调 / Charter 衬线标题与引用,无衬线正文,等宽体承载全大写微标签 / 10px 圆角卡片、1px 细边框,无投影,引用块配强调色左边线 / 单栏纸面文档流、紧凑正文、元信息条与局部分栏信息块 / 严肃、克制、秩序感
|
|
160
157
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
## 1. 按角色读,不按名字读
|
|
18
18
|
|
|
19
|
-
风格的 token
|
|
19
|
+
风格的 token 名不可信:不同风格会用同一个 token 名表达完全不同的角色,也会用不同名字表达同一种角色;`accent` 就可能是品牌主色、细线或点睛色。不要按名字机械映射,必须先看值和使用位置。
|
|
20
20
|
|
|
21
21
|
所以先判角色,再落槽位:
|
|
22
22
|
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
- **`--x-foreground` 是「压在 x 上的文字色」**,不是 x 的浅色底。风格里的 `success-bg` / `danger-bg` 这类徽章底色角色相反,按 §3 另加变量。
|
|
40
40
|
- **风格里叫 `secondary` 的通常是第二数据色**(图表第二序列),不是 shadcn 的次级面。`--secondary` 该由「比画布深/浅一档的面」来填。
|
|
41
|
-
- **风格里叫 `accent`
|
|
41
|
+
- **风格里叫 `accent` 的可能是任何东西**——品牌主色、细线或点睛色。看值和用法,别看名。
|
|
42
42
|
|
|
43
43
|
## 2. 缺源怎么办(这是多数情况,不是例外)
|
|
44
44
|
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
|
|
47
47
|
- **缺 `primary`**:风格若明确宣称无强调色(暗夜鎏金、夜航灯塔那类),`--primary` 填最高对比的中性色(深色风格填最亮的 ink,浅色风格填最深的 ink),`--primary-foreground` 填画布色。**不要自己发明一个强调色。**
|
|
48
48
|
- **缺 `success` / `danger` / `warning`**:保留语义可辨的最低要求,但把饱和度和明度拉到风格的水位——取模板默认的色相,饱和度与明度对齐风格里已有的彩色 token。**风格一个彩色 token 都没有时(暗夜鎏金那类),饱和度取 35% 为下限**,不要跟着中性色降到 7%——那样红绿橙互相分不出来。这种情况下状态色只用于 badge 与文字,不做面。风格明确禁止某类色(克莱因蓝的双色绝对主义、暗夜鎏金的严格中性)时,只在状态标记这一个位置破例,其余不用。
|
|
49
|
-
- **缺 chart-1..5**:从 `--primary` 的色相出发生成 5
|
|
49
|
+
- **缺 chart-1..5**:从 `--primary` 的色相出发生成 5 档单色阶(改明度与饱和度,不引入新色相),不要直接套模板默认图表色。
|
|
50
50
|
|
|
51
51
|
## 3. 风格专有 token 不塞进槽位
|
|
52
52
|
|
|
@@ -17,7 +17,7 @@ file-match-pattern:
|
|
|
17
17
|
|------|----------------------|--------------------------|
|
|
18
18
|
| 鉴权 | 写操作加 `@NeedLogin()` | **不加** `@NeedLogin()`,鉴权在网关层通过 API Key 完成 |
|
|
19
19
|
| 用户身份 | `req.userContext.userId` 区分用户 | 统一走系统身份,不依赖 `userId` 做业务区分 |
|
|
20
|
-
| 通讯录 |
|
|
20
|
+
| 通讯录 | 前端按登录态能力取人 | 先确认非空 `appId`;无法确认时仅返回稳定 ID,展示值可空 / 可选 |
|
|
21
21
|
| Controller 文件 | `xxx.controller.ts` | `xxx.openapi.controller.ts`,放在同一 module 下 |
|
|
22
22
|
| OpenAPI 文档 | 不需要 | **必须**同步维护 `docs/openapi.json`(见下文) |
|
|
23
23
|
|
|
@@ -62,75 +62,44 @@ findAll(@Req() req: Request) {
|
|
|
62
62
|
|
|
63
63
|
## 用户信息(通讯录)—— 强制规则
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
`userId` 标识当前用户,`appId` 标识当前应用,业务记录中的人员 ID 不是二者。按以下顺序处理 `/openapi` 人员字段:
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
1. 区分 `userId`、`appId` 与要展示的 `miaoda_user_id`。
|
|
68
|
+
2. 确认已支持的运行时机制是否为请求上下文提供**非空 `appId`**;给定人员 ID 或没有当前 `userId` 都不能证明该条件,不得凭空补身份头。
|
|
69
|
+
3. 再选择人员查询能力并定义响应字段语义。
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
export class XxxOpenApiController {
|
|
73
|
-
constructor(private readonly authn: AuthNPaasService) {}
|
|
74
|
-
|
|
75
|
-
@Get(':id')
|
|
76
|
-
async get(@Param('id') id: string) {
|
|
77
|
-
// 不要 try/catch 吞掉——平台故障必须暴露给调用方,别降级成「查无此人」
|
|
78
|
-
const [user] = await this.authn.listUsersByIds([id]);
|
|
79
|
-
if (!user) throw new NotFoundException(`用户 ${id} 不存在`);
|
|
80
|
-
return {
|
|
81
|
-
miaodaUserId: id,
|
|
82
|
-
name: user.name?.zh_cn ?? user.name?.en_us ?? '', // name 是 I18nText,不是字符串
|
|
83
|
-
avatar: user.avatar?.image?.large ?? '', // 头像 URL 在 image.large 上
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
71
|
+
### 请求上下文有非空 appId
|
|
88
72
|
|
|
89
|
-
|
|
73
|
+
允许从统一入口调用 `AuthNPaasService.listUsersByIds`。它不要求当前 `userId`,但要求非空 `appId`;`PlatformModule.forRoot()` 已注册 global 的 `AuthNPaasModule`,不要另加 `providers` 或直接依赖。
|
|
90
74
|
|
|
91
|
-
|
|
92
|
-
|
|
75
|
+
```typescript
|
|
76
|
+
import { AuthNPaasService } from '@lark-apaas/fullstack-nestjs-core';
|
|
93
77
|
|
|
94
|
-
|
|
78
|
+
const [user] = await this.authn.listUsersByIds([miaodaUserId]);
|
|
79
|
+
if (!user) throw new NotFoundException(`用户 ${miaodaUserId} 不存在`);
|
|
80
|
+
return {
|
|
81
|
+
miaodaUserId,
|
|
82
|
+
name: user.name?.zh_cn ?? user.name?.en_us,
|
|
83
|
+
avatar: user.avatar?.image?.large,
|
|
84
|
+
};
|
|
85
|
+
```
|
|
95
86
|
|
|
96
|
-
|
|
97
|
-
|---|---|
|
|
98
|
-
| 复用应用里已有的用户 / 用户资料 service | 那些多半是给浏览器写的,依赖登录态;`/openapi` 下没有登录用户,会静默返回空 |
|
|
99
|
-
| 自己写 HTTP 请求裸调平台通讯录接口 | 鉴权 / 重试 / 日志 / trace 都在 SDK 里,裸调必然漏 |
|
|
100
|
-
| 用 `req.userContext.userId` 做用户查询 | `/openapi` 下**恒为空** |
|
|
101
|
-
| 用 `AuthNPaasService.getCurrentUserLarkUserId()` | 它读 `userId`,取不到只打日志返回 `null`、**不报错**,故障静默 |
|
|
102
|
-
| 从数据库表里 join 出人名 / 头像 | 通讯录不是应用数据,会过期且不 follow 权限 |
|
|
103
|
-
| 按关键词搜人 / 搜部门 / 搜群 | 结果取决于「谁在搜」,`/openapi` 下没有调用者视角,**服务端不提供** |
|
|
104
|
-
| `try/catch` 吞掉 `listUsersByIds` 抛的错、返回空值 | 「平台挂了」和「查无此人」是两回事,吞掉就没人知道故障 |
|
|
105
|
-
| 用 `getBatchLarkUserIds` 判断用户是否存在 | 它是 ID 转换,**外部用户本来就没有 employeeId**,会把外部用户误判成不存在 |
|
|
106
|
-
| 服务端拿不到的字段用 `''` 占位塞进响应 | 调用方会以为字段存在只是没值。拿不到就**从响应类型里删掉**,并告诉用户改走飞书开放平台通讯录 API |
|
|
87
|
+
批量调用的返回值与入参等长同序;`name` 是 `I18nText`,需映射语言字段。SDK 平台错误与返回 `null` 的查无此人语义必须不同,不要 `try/catch` 吞掉平台错误。
|
|
107
88
|
|
|
108
|
-
|
|
109
|
-
// ❌ 错误:包了一层应用现有的用户服务
|
|
110
|
-
const profile = await this.userProfileService.findById(id);
|
|
111
|
-
|
|
112
|
-
// ❌ 错误:OpenAPI 态搜不了人
|
|
113
|
-
const users = await someSearchApi({ query: keyword });
|
|
114
|
-
|
|
115
|
-
// ❌ 错误:读 userId,OpenAPI 态恒为空且失败不报错
|
|
116
|
-
const larkId = await this.authn.getCurrentUserLarkUserId();
|
|
117
|
-
|
|
118
|
-
// ❌ 错误:吞掉平台故障 + 拿不到的字段用空串占位
|
|
119
|
-
let name = '';
|
|
120
|
-
try {
|
|
121
|
-
const [u] = await this.authn.listUsersByIds([id]);
|
|
122
|
-
name = u?.name?.zh_cn ?? '';
|
|
123
|
-
} catch { /* 平台挂了也当查无此人 */ }
|
|
124
|
-
return { userId: id, name, department: '', email: '', jobTitle: '' };
|
|
125
|
-
```
|
|
89
|
+
### 匿名入口无法保证 appId
|
|
126
90
|
|
|
127
|
-
|
|
91
|
+
主体资源读取不得依赖人员查询。返回业务数据中已有的稳定 `miaodaUserId`;在 `shared/api.interface.ts` 与 `docs/openapi.json` 中将 `name`、`avatar` 等展示值建模为可空或可选,不要用空字符串或固定“未知用户”冒充查询结果。用户明确要求匿名响应必须返回真实姓名、但当前没有受支持的 `appId` 来源时,报告运行时能力阻塞。
|
|
128
92
|
|
|
129
|
-
|
|
93
|
+
### 禁止(以下任一出现即为错误实现)
|
|
130
94
|
|
|
131
|
-
|
|
95
|
+
- 凭空补身份头,或从 `userId` / 人员 ID 推导 `appId`
|
|
96
|
+
- 复用依赖登录态的用户资料 service,或自己写 HTTP 裸调平台通讯录接口
|
|
97
|
+
- 在数据库持久化姓名 / 头像副本再 join 返回
|
|
98
|
+
- 用 service 广域 `try/catch` 吞掉查询错误,或固定返回“未知用户”
|
|
99
|
+
- 用 `getCurrentUserLarkUserId()` 查目标用户,或用 `getBatchLarkUserIds()` 判断用户是否存在
|
|
100
|
+
- 按关键词搜人 / 搜部门 / 搜群;这需要调用者视角,服务端不提供
|
|
132
101
|
|
|
133
|
-
|
|
102
|
+
`AuthNPaasService` import 不到时,报告阻塞并请人升级 core 版本;不要加 `@lark-apaas/nestjs-authnpaas` 直接依赖或自实现替代品。手机号 / 职位 / 工号 / 直属上级 / 在职状态以及按 ID 查群不属于该能力,需使用受支持的前端或飞书开放平台路径。
|
|
134
103
|
|
|
135
104
|
完整能力边界与字段口径见 [`contacts-service` › 第六节](../contacts-service/SKILL.md)。
|
|
136
105
|
|
|
@@ -6,6 +6,15 @@ steering-topic: plugin_guide
|
|
|
6
6
|
match-template-name: nestjs-react-fullstack
|
|
7
7
|
gate-tools:
|
|
8
8
|
- tool: plugin_instance
|
|
9
|
+
- tool: api_request
|
|
10
|
+
when-contains:
|
|
11
|
+
url: "/api/capability"
|
|
12
|
+
- tool: bash
|
|
13
|
+
when-contains:
|
|
14
|
+
command: "/api/capability"
|
|
15
|
+
- tool: bash
|
|
16
|
+
when-contains:
|
|
17
|
+
command: "__innerapi__/capability"
|
|
9
18
|
---
|
|
10
19
|
|
|
11
20
|
# Plugin 集成指南
|
|
@@ -20,6 +29,7 @@ gate-tools:
|
|
|
20
29
|
| 获取运行时投影 | 调用 `get_plugin_ai_json(pluginInstanceId)` |
|
|
21
30
|
| Client 侧调用 | `capabilityClient.load(id).call(actionKey, input)`(流式用 `callStream`) |
|
|
22
31
|
| Server 侧调用(仅兜底) | `capabilityService.load(id).call(actionKey, input)` |
|
|
32
|
+
| 手动 HTTP/debug 调试 | 仅调 `/api/capability/<id>`、`/api/capability/<id>/stream`、`/__innerapi__/capability/debug/<id>` 或 `/__innerapi__/capability/debug/<id>/stream` 时,body 用 action/params:`{ "action": "<actionKey>", "params": { ... } }`;禁止在 body 顶层放 `actionKey`、`input` |
|
|
23
33
|
| 长耗时 AI 结果 | 大体量/多字段/多份/多语言/文件或多模态串联等结构信号命中时,优先前端 `callStream` 渐进展示;需保存则流式结束后复用 CRUD 落库;仅在 Client 侧无法满足时使用后端任务记录 + 状态查询 + 结果读取;禁止单个 HTTP 请求等待完整结果后才返回 |
|
|
24
34
|
| capabilityClient 导入 | `import { capabilityClient } from '@lark-apaas/client-toolkit'` |
|
|
25
35
|
| CapabilityService 导入 | `import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';` |
|
|
@@ -40,9 +50,9 @@ gate-tools:
|
|
|
40
50
|
|
|
41
51
|
**paramsSchema 仅支持 4 种参数类型**:文本 `{ "type": "string" }`、字符串数组 `{ "type": "array", "items": { "type": "string" } }`、图片 `{ "type": "string", "format": "picture" }`、文件 `{ "type": "string", "format": "file" }`,均需带 `description`。
|
|
42
52
|
|
|
43
|
-
> **文件类参数**:`file` / `picture` / `plugin-file-url` / `plugin-image-url` 字段,Client 侧原始 File/Blob 直接传 `capabilityClient
|
|
53
|
+
> **文件类参数**:`file` / `picture` / `plugin-file-url` / `plugin-image-url` 字段,Client 侧原始 File/Blob 直接传 `capabilityClient`,禁止先经 dataloom 上传再传 URL。已持久化文件/图片由 Server 侧 `CapabilityService` 调 AI 插件时,优先取 `file_path`(或 `bucket_id + file_path`)用 `FileService.createSignedUrl(filePath, 3600)` / `fileService.from(bucketId).createSignedUrl(filePath, 3600)` 签成临时 http(s) URL 后传入。旧记录只有标准 `/app` 或 `/spark/app/.../runtime/api/v1/storage/object/<bucket>/<key>` download URL 时,必须先解析其中的 bucket 与 file path,再调用 `fileService.from(bucketId).createSignedUrl(filePath, 3600)`;默认 `createSignedUrl()` 只使用默认 bucket,不能从 URL 切换 bucket。签名失败或返回空 URL 时要 `warn`,业务状态保持 `failed` / `pending` / `null`,不写成功态。禁止给标准路径拼接 `localhost`、`window.location.origin` 或 `/app/<app_id>`;非标准相对路径、前端展示 URL、非法 data URL 不作稳定入参。
|
|
44
54
|
>
|
|
45
|
-
>
|
|
55
|
+
> **稳定契约**:已持久化文件不得先下载再转 base64;应统一使用签名 URL。缺少可识别的文件路径、签名失败或返回空 URL 时记录警告,写入 `failed` / `pending` / `null`,并停止调用插件;不得改用原始 URL。完整示例见 [Server 侧已持久化文件进入 AI capability](references/plugin-coding-guide.md#3-server-侧已持久化文件进入-ai-capability)。
|
|
46
56
|
|
|
47
57
|
## 可用的 Plugin
|
|
48
58
|
|
|
@@ -76,7 +86,7 @@ gate-tools:
|
|
|
76
86
|
- 调用侧:优先 Client(`unary` → `call()`,`stream` → `callStream()`);触发器/定时任务、敏感凭证、强事务、结果需落库 → Server 侧
|
|
77
87
|
- 高耗时 AI capability 闸门:若运行时投影或功能设计显示输出规模大、输出字段多、需要多份结果、多语言/长文本、文件或多模态输入后继续生成、多个 capability 串联、或结果需后续查看/落库,禁止把完整生成放进一个同步 HTTP 请求等待;能由前端承接时优先 `callStream` 渐进展示,需保存则流式结束后复用 CRUD 落库;仅在 Client 侧无法满足时后端创建任务记录后快速返回、由前端短轮询状态/结果,或拆成多个独立小调用
|
|
78
88
|
4. **代码放置**:Client(默认,用户交互触发)→ `client/` 组件/hooks;Server(兜底)→ `server/` Service。
|
|
79
|
-
5. **真实调用冒烟(完成前必须)**:至少成功调用一次 `call()` 或 `callStream()`(按 outputSchema 读 chunk
|
|
89
|
+
5. **真实调用冒烟(完成前必须)**:至少成功调用一次 SDK `call(actionKey, input)` 或 `callStream(actionKey, input)`(按 outputSchema 读 chunk);失败日志含最小字段(见 `references/plugin-coding-guide.md`)。绕过 SDK 手动复测 capability HTTP/debug 入口前,先读 `references/plugin-coding-guide.md` 的“服务端 capability 手动 HTTP 调试与排障”;无冒烟结果不得宣告完成。
|
|
80
90
|
|
|
81
91
|
### Plugin Chain 调用示例
|
|
82
92
|
|
|
@@ -151,6 +161,8 @@ const structured = await capabilityClient
|
|
|
151
161
|
|
|
152
162
|
**创建 `ai-text-to-json` 实例时**:一次性定义所有提取字段(参考 schema/表单/UI),最多 20 个。字段类型以当次 `formSchema.paramType.enum` 为准;静态指南/`readme` 只解释 enum 内类型,冲突时按 enum 建模并说明冲突,不得降级为 String;enum 缺失、不可读或缺业务所需类型时停止并报告合同冲突。基础字段用 String/Number/Boolean 等对应类型,同构列表用 Array,固定字段集合用 Object,嵌套成员写进 `paramDescription`。对象/列表结果不得序列化进 String;只有业务要求原样透传、不解析、不改写 JSON 文本时才用 String。创建后调用 `get_plugin_ai_json`,按 `outputSchema` 核验字段并生成调用代码;按契约直接消费结果,禁止无依据 `JSON.parse`。
|
|
153
163
|
|
|
164
|
+
`jsonStructure` 的顶级字段都会参与必填校验:模型漏任一 key 时整次调用抛 `INVALID_OUTPUT`。插件只用文本 prompt,不用 JSON Schema 强制生成;顶级字段越多,整体失败概率越高。N 套同构结果用一个 Array 字段承载,元素结构写进 `paramDescription`,不要摊成 N x M 个顶级字段。Array/Object 内部只做 `z.any()` 校验,`paramDescription` 仅影响 prompt;消费方仍须校验元素数量、成员字段和空值。
|
|
165
|
+
|
|
154
166
|
**创建 `ai-image-to-json` 实例时**:必须一次性定义**所有**需提取字段(参考数据库 schema / 表单定义 / UI 设计),宁多勿漏;字段类型仅支持 String/Number/Boolean,最多 20 个;先调 `get_plugin_ai_json` 确认上游插件的 `outputSchema` 确保输入格式正确。
|
|
155
167
|
|
|
156
168
|
## 多维表格数据架构
|
|
@@ -273,6 +285,6 @@ const url = `https://${req.hostname}${process.env.CLIENT_BASE_PATH}${routePath}`
|
|
|
273
285
|
| 流式 chunk 当字符串拼接(`text += chunk`) | chunk 是对象,按 outputSchema 解构:`text += chunk.content \|\| ''` |
|
|
274
286
|
| formValue 用 `{% raw %}["{{input.xxx}}"]{% endraw %}` 包装已是 array 的 paramsSchema 参数 | paramsSchema 为 array 时 formValue 透传 `{% raw %}"{{input.xxx}}"{% endraw %}`,不再包一层数组 |
|
|
275
287
|
| 前端调插件后不保存结果(页面刷新丢失),或为保存结果单独新建 API 端点 | 需持久化时 Server 侧调用直接落库(优先),或 Client 侧调用后立即经**已有** CRUD 接口保存 |
|
|
276
|
-
| Server 侧把非标准相对路径、误拼 origin 的 storage object path、前端展示 URL 或非法 base64 data URL 传给 `file` / `picture` / `plugin-file-url` / `plugin-image-url` 参数 |
|
|
288
|
+
| Server 侧把非标准相对路径、误拼 origin 的 storage object path、前端展示 URL 或非法 base64 data URL 传给 `file` / `picture` / `plugin-file-url` / `plugin-image-url` 参数 | 已持久化文件优先用 `file_path` 或 `bucket_id + file_path` 生成 1h 签名 http(s) URL 后再传;标准 storage download URL 要先解析 bucket 与 file path,再从对应 bucket 签名 |
|
|
277
289
|
| 创建了 PluginInstance 但只建不调 | CREATE 后必须接 `get_plugin_ai_json` → 生成调用代码 → 集成业务逻辑 |
|
|
278
290
|
| 插件返回值 `as any` 直接取字段 | 按 outputSchema 生成 TypeScript interface |
|