@lark-apaas/coding-steering 0.1.57 → 0.1.58
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 +3 -1
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +4 -1
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +66 -2
- package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +34 -11
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +3 -0
package/package.json
CHANGED
package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md
CHANGED
|
@@ -12,6 +12,7 @@ match-template-name: nestjs-react-fullstack
|
|
|
12
12
|
|
|
13
13
|
- **优先用 `download_url` 形式(默认按这个来)**:上传后取 `data.download_url`,存到 **`text` 类型字段**;渲染 / 下载直接用这个 URL,最简单,绝大多数场景都用它。
|
|
14
14
|
- 仅当业务明确使用 **`file_attachment` 类型字段**时才走 file_path:该字段存 `bucket_id` + `file_path`(`file_path` 只能是文件路径,**不可以存 `download_url`**);要拿 URL 用 dataloom SDK 的 `generateDownloadUrlFromFilePath(file_path)`,且 `file_path` 必须从该字段读出,禁止自己拼接。
|
|
15
|
+
- 上传后的文件还要给后端 AI capability 解析/理解时,前端展示仍用 `download_url`;提交给后端的必须是 `file_path`(以及可用的 `bucket_id`)。后端用 `FileService.createSignedUrl(filePath, 3600)` 或 `fileService.from(bucketId).createSignedUrl(filePath, 3600)` 生成临时 http(s) URL 再传插件。前端不要构造 signed URL;普通展示/下载仍默认用 `download_url`。
|
|
15
16
|
|
|
16
17
|
# dataloom SDK 文件服务
|
|
17
18
|
|
|
@@ -24,6 +25,7 @@ match-template-name: nestjs-react-fullstack
|
|
|
24
25
|
- **沙箱 dev 限制**:`uploadFile` 打的 `/app/<appId>/__runtime__/api/v1/storage/object/<bucket>/pre_upload` **在沙箱 dev 下恒 404**,只有发布态可用。这是环境限制不是代码缺陷,按本 skill 写法即为正确,不要为它改代码;开发期要验证上传链路改走接口测试直接打后端接口,或复用库里已有的文件 URL。详见 coding-guide「沙箱 dev 不提供平台 runtime 接口」。
|
|
25
26
|
- 上传成功后,最重要的返回值是 `data.download_url`。需要将此URL保存到你的业务数据库中
|
|
26
27
|
- **⚠️ 场景区分(重要)**:`dataloom.storage` 仅适用于需要持久化存储文件或获取 `download_url` 保存到数据库的场景。如果文件仅作为插件输入(传给 `capabilityClient`),**必须直接传 File/Blob 对象,禁止先走 dataloom 上传再传 URL**;插件调用(capability)不属于 dataloom,详见 plugin-guide
|
|
28
|
+
- **Server 侧 AI capability 边界**:文件已上传并持久化后,若由后端调用 `CapabilityService` 做文档解析/图片理解,不要把前端展示用 `download_url` 当插件入参;把 `file_path` 和 `bucket_id` 传给后端,由后端 `FileService` 签成临时 http(s) URL。
|
|
27
29
|
- **download_url 格式说明**:`download_url` 返回的可能是相对路径(如 `/spark/app/.../storage/object/...`),这是正常行为。**禁止**在前面拼接 `window.location.origin` 或其他域名前缀,平台会自动解析相对路径。直接使用原始值即可。
|
|
28
30
|
- **文件URL**:通过 `generateDownloadUrlFromFilePath` 方法获取文件的链接时,传入的 file_path 必须是从数据库中 `file_attachment` 类型字段读出来的,禁止自己拼接路径。
|
|
29
31
|
|
|
@@ -323,7 +325,7 @@ export default FileUploadDemo;
|
|
|
323
325
|
|
|
324
326
|
| 要点 | 说明 |
|
|
325
327
|
|------|------|
|
|
326
|
-
| 上传流程 |
|
|
328
|
+
| 上传流程 | 展示/下载:`uploadFile(file)` → 取 `data.download_url` → POST 后端保存;后端 AI 解析:同时保存/提交 `data.file_path` 与 `data.bucket_id` 供后端签名 |
|
|
327
329
|
| 文件对象 | 直接传原始 `File`,**禁止**包装或修改文件名 |
|
|
328
330
|
| 组件依赖 | shadcn/ui `Button`/`Card` + `lucide-react` + `sonner` |
|
|
329
331
|
| 请求实例 | 必须用 `axiosForBackend`,禁止 `fetch` |
|
|
@@ -40,7 +40,9 @@ gate-tools:
|
|
|
40
40
|
|
|
41
41
|
**paramsSchema 仅支持 4 种参数类型**:文本 `{ "type": "string" }`、字符串数组 `{ "type": "array", "items": { "type": "string" } }`、图片 `{ "type": "string", "format": "picture" }`、文件 `{ "type": "string", "format": "file" }`,均需带 `description`。
|
|
42
42
|
|
|
43
|
-
> **文件类参数**:`
|
|
43
|
+
> **文件类参数**:`file` / `picture` / `plugin-file-url` / `plugin-image-url` 字段,Client 侧原始 File/Blob 直接传 `capabilityClient`。已持久化文件/图片由 Server 侧 `CapabilityService` 调 AI 插件时,取 `file_path`(或 `bucket_id + file_path`)用 `FileService.createSignedUrl(filePath, 3600)` / `fileService.from(bucketId).createSignedUrl(filePath, 3600)` 签成临时 http(s) URL 后传入。签名失败要 `warn`,业务状态保持 `failed` / `pending` / `null`,不写成功态。标准 `/app` 或 `/spark/app/.../runtime/api/v1/storage/object/<bucket>/<key>` 是可被平台 resolver 消费的内部路径,但不是 `createSignedUrl` 的 `filePath` 入参;禁止给它拼接 `localhost`、`window.location.origin` 或 `/app/<app_id>`。非标准相对路径、前端展示 URL、非法 data URL 不作稳定入参。
|
|
44
|
+
>
|
|
45
|
+
> **改造已有调用**:检查所有文档和图片插件调用点,统一使用上面的签名 URL。删除下载后转 base64 的旧逻辑。缺少 `file_path` 或签名失败时记录警告,写入 `failed` / `pending` / `null`,并停止调用插件;不得改用原始 URL。完整示例见 [Server 侧已持久化文件进入 AI capability](references/plugin-coding-guide.md#3-server-侧已持久化文件进入-ai-capability)。
|
|
44
46
|
|
|
45
47
|
## 可用的 Plugin
|
|
46
48
|
|
|
@@ -271,5 +273,6 @@ const url = `https://${req.hostname}${process.env.CLIENT_BASE_PATH}${routePath}`
|
|
|
271
273
|
| 流式 chunk 当字符串拼接(`text += chunk`) | chunk 是对象,按 outputSchema 解构:`text += chunk.content \|\| ''` |
|
|
272
274
|
| formValue 用 `{% raw %}["{{input.xxx}}"]{% endraw %}` 包装已是 array 的 paramsSchema 参数 | paramsSchema 为 array 时 formValue 透传 `{% raw %}"{{input.xxx}}"{% endraw %}`,不再包一层数组 |
|
|
273
275
|
| 前端调插件后不保存结果(页面刷新丢失),或为保存结果单独新建 API 端点 | 需持久化时 Server 侧调用直接落库(优先),或 Client 侧调用后立即经**已有** CRUD 接口保存 |
|
|
276
|
+
| 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 object path 原样经过平台 resolver 时可消费,但不能直接作为 `createSignedUrl` 的 `filePath` |
|
|
274
277
|
| 创建了 PluginInstance 但只建不调 | CREATE 后必须接 `get_plugin_ai_json` → 生成调用代码 → 集成业务逻辑 |
|
|
275
278
|
| 插件返回值 `as any` 直接取字段 | 按 outputSchema 生成 TypeScript interface |
|
package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md
CHANGED
|
@@ -332,7 +332,70 @@ Please make sure that the argument Function at index [0] is available in the Xxx
|
|
|
332
332
|
|
|
333
333
|
同一规则适用于从 `@lark-apaas/fullstack-nestjs-core` 注入的其他平台服务(`AuthNPaasService`、`FileService` 等)。
|
|
334
334
|
|
|
335
|
-
#### 3.
|
|
335
|
+
#### 3. Server 侧已持久化文件进入 AI capability
|
|
336
|
+
|
|
337
|
+
Client 侧拿到用户刚选择的原始 `File` / `Blob` 时,直接传 `capabilityClient`,不要为了调用插件先上传到应用存储。文件/图片已作为业务数据持久化,且必须由 Server 侧 `CapabilityService` 调 `ai-doc-parser`、`ai-image-understanding` 或其它 `file` / `picture` / `plugin-file-url` / `plugin-image-url` 参数时,才在服务端把存储路径换成临时 http(s) URL 后传入插件。
|
|
338
|
+
|
|
339
|
+
优先从业务记录读取存储 `file_path`;字段类型是 `file_attachment` 时,同时读取 `bucket_id` 与 `file_path`。`createSignedUrl` 接收的是 bucket 内的 `file_path`,不要把完整的 storage object path 直接传入。标准 `/app` 或 `/spark/app/.../runtime/api/v1/storage/object/<bucket>/<key>` 原样经过平台 resolver 时可以消费;禁止给它拼接 `localhost`、`window.location.origin` 或 `/app/<app_id>`,否则会绕过内部路径解析并被当成不安全的外部 URL。非标准相对路径、前端展示 URL、非法 base64 data URL 不应作为 Server 侧插件稳定入参。
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { FileService } from '@lark-apaas/fullstack-nestjs-core';
|
|
343
|
+
|
|
344
|
+
type StoredFileRef = {
|
|
345
|
+
file_path?: string | null;
|
|
346
|
+
bucket_id?: string | null;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
async function createTemporaryFileUrl(
|
|
350
|
+
fileService: FileService,
|
|
351
|
+
file: StoredFileRef,
|
|
352
|
+
): Promise<string | null> {
|
|
353
|
+
if (!file.file_path) return null;
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
if (file.bucket_id) {
|
|
357
|
+
return await fileService
|
|
358
|
+
.from(file.bucket_id)
|
|
359
|
+
.createSignedUrl(file.file_path, 3600);
|
|
360
|
+
}
|
|
361
|
+
return await fileService.createSignedUrl(file.file_path, 3600);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
// 这里不要返回伪成功 URL;由调用方把业务状态置为 failed/pending/null 等可见状态
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
调用 AI 插件前先签名并校验 URL:
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
const docParserPluginInstanceId = '<doc-parser-plugin-instance-id>';
|
|
373
|
+
const signedUrl = await createTemporaryFileUrl(this.fileService, storedFile);
|
|
374
|
+
if (!signedUrl) {
|
|
375
|
+
this.logger.warn('missing file_path or create signed url failed before AI capability call', {
|
|
376
|
+
filePath: storedFile.file_path,
|
|
377
|
+
bucketId: storedFile.bucket_id,
|
|
378
|
+
pluginInstanceId: docParserPluginInstanceId,
|
|
379
|
+
actionKey: 'parseDocToMarkdown',
|
|
380
|
+
});
|
|
381
|
+
await this.updateRecord(recordId, { parseStatus: 'failed', parsedText: null });
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const output = await this.capabilityService
|
|
386
|
+
.load(docParserPluginInstanceId)
|
|
387
|
+
.call('parseDocToMarkdown', {
|
|
388
|
+
fileUrl: [signedUrl], // 是否数组必须以 inputSchema 为准
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
改造已有调用时:
|
|
393
|
+
|
|
394
|
+
- 检查所有文档和图片插件调用点,统一使用签名函数返回的 URL。
|
|
395
|
+
- 删除下载后转 base64 的旧逻辑,也不要把原始 URL 作为备用值传给插件。
|
|
396
|
+
- 缺少 `file_path` 或签名失败时记录警告,写入 `failed` / `pending` / `null`,并停止调用插件。
|
|
397
|
+
|
|
398
|
+
#### 4. 调用示例
|
|
336
399
|
|
|
337
400
|
服务端 `CapabilityExecutor` 的**三个方法全都没有泛型参数**(`@lark-apaas/nestjs-capability`):
|
|
338
401
|
|
|
@@ -366,11 +429,12 @@ try {
|
|
|
366
429
|
}
|
|
367
430
|
```
|
|
368
431
|
|
|
369
|
-
####
|
|
432
|
+
#### 5. Server 侧编排与容错原则
|
|
370
433
|
|
|
371
434
|
- PluginInstance 调用在 Server 侧通常属于 **外部依赖 / side-effect**
|
|
372
435
|
- 除非业务明确要求强一致性,**默认不应阻塞主业务流程**
|
|
373
436
|
- 已选择 Server 侧承接的高耗时 AI capability 必须有可观测状态:创建任务时记录处理进度、完成终态、失败终态、输入摘要、错误信息和结果引用;触发接口只返回任务标识与当前状态,前端通过短轮询读取进度和最终结果
|
|
437
|
+
- 文件/图片签名 URL 生成失败时必须 `warn` 并把业务记录保持在 `failed` / `pending` / `null` 等用户可见状态,禁止构造假 URL 或写入“解析成功”
|
|
374
438
|
|
|
375
439
|
推荐写法:异步触发 + catch 兜底:
|
|
376
440
|
|
package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md
CHANGED
|
@@ -24,10 +24,12 @@ match-template-name: nestjs-react-fullstack
|
|
|
24
24
|
|
|
25
25
|
| 方法 | 说明 | 返回值 |
|
|
26
26
|
|------|------|--------|
|
|
27
|
-
| `upload(file, options?)` | 上传文件 | `FileMeta` |
|
|
28
|
-
| `download(path)` | 下载文件(≤50MB) | `FileDownloadBuilder` |
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
27
|
+
| `upload(file, options?)` | 上传文件 | `FileMeta` |
|
|
28
|
+
| `download(path)` | 下载文件(≤50MB) | `FileDownloadBuilder` |
|
|
29
|
+
| `createSignedUrl(filePath, expiresIn)` | 默认 bucket 下生成临时 http(s) 签名 URL | `string` |
|
|
30
|
+
| `from(bucketId).createSignedUrl(filePath, expiresIn)` | 指定 bucket 生成临时 http(s) 签名 URL | `string` |
|
|
31
|
+
| `remove(filePaths)` | 删除文件 | `RemoveResponse` |
|
|
32
|
+
| `getFileMetadata(filePath)` | 获取文件元信息 | `FileMeta` |
|
|
31
33
|
|
|
32
34
|
## 注入 FileService
|
|
33
35
|
|
|
@@ -123,10 +125,30 @@ const { content, metadata } = await this.fileService
|
|
|
123
125
|
.asStream();
|
|
124
126
|
|
|
125
127
|
// 不推荐:Blob 下载(限制 50MB,会将整个文件加载到内存)
|
|
126
|
-
const { content, metadata } = await this.fileService.download(downloadURL);
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### 3.
|
|
128
|
+
const { content, metadata } = await this.fileService.download(downloadURL);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 3. 生成临时签名 URL
|
|
132
|
+
|
|
133
|
+
服务端把应用存储中的文件/图片传给 AI capability(如 `ai-doc-parser`、`ai-image-understanding` 或 `plugin-file-url` / `plugin-image-url` 入参)时,不要把 `downloadURL`、前端展示 URL、非标准相对路径或 base64 data URL 当稳定入参。优先用数据库保存的 `file_path`,必要时带上 `bucket_id`,生成临时 http(s) 签名 URL 后再传插件。
|
|
134
|
+
|
|
135
|
+
`download()` 是服务端自己读取文件内容的接口,不是给 AI capability 准备 URL 的默认方案;需要给插件一个可访问 URL 时用 `createSignedUrl()`。
|
|
136
|
+
|
|
137
|
+
**示例:**
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// 默认 bucket
|
|
141
|
+
const signedUrl = await this.fileService.createSignedUrl(filePath, 3600);
|
|
142
|
+
|
|
143
|
+
// 指定 file_attachment 记录里的 bucket
|
|
144
|
+
const signedUrl = await this.fileService
|
|
145
|
+
.from(bucketId)
|
|
146
|
+
.createSignedUrl(filePath, 3600);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
签名失败时记录 `warn`,业务状态保持为 `failed`、`pending` 或结果字段 `null`,让前端能看到失败/待处理;禁止吞掉错误后写入“已解析成功”。
|
|
150
|
+
|
|
151
|
+
### 4. 删除文件
|
|
130
152
|
|
|
131
153
|
**入参:**
|
|
132
154
|
|
|
@@ -146,7 +168,7 @@ const result = await this.fileService.remove([fileMeta.downloadURL]);
|
|
|
146
168
|
const result = await this.fileService.remove(['file1.pdf', 'file2.png']);
|
|
147
169
|
```
|
|
148
170
|
|
|
149
|
-
###
|
|
171
|
+
### 5. 获取文件元信息
|
|
150
172
|
|
|
151
173
|
**入参:**
|
|
152
174
|
|
|
@@ -170,8 +192,9 @@ const meta = await this.fileService.getFileMetadata('file.pdf');
|
|
|
170
192
|
|
|
171
193
|
| 错误 | 正确做法 |
|
|
172
194
|
|------|----------|
|
|
173
|
-
| 直接 `await download()` 导致内存溢出 | 始终使用 `.asStream()` 流式下载 |
|
|
174
|
-
|
|
|
195
|
+
| 直接 `await download()` 导致内存溢出 | 始终使用 `.asStream()` 流式下载 |
|
|
196
|
+
| 把 `download()` 结果或前端 `downloadURL` 当作 AI capability URL 入参 | 用 `createSignedUrl(filePath, 3600)` 生成临时 http(s) URL 后再传插件 |
|
|
197
|
+
| 忘记设置 `contentType` 导致浏览器无法预览 | 上传时明确指定 `contentType` |
|
|
175
198
|
| 直接 `new FileService()` 手动实例化 | 通过 NestJS DI 注入 `FileService` |
|
|
176
199
|
| 在异步回调中调用 `download()` 导致上下文丢失 | 在请求处理函数中立即调用,SDK 内部已处理上下文捕获 |
|
|
177
200
|
| 删除时传单个字符串 | `remove()` 参数为 `string[]` 数组 |
|
|
@@ -42,6 +42,8 @@ lark-cli apps --help 2>&1 | grep -q '+plugin-install' && echo "READY" || echo "M
|
|
|
42
42
|
| 插件实例配置位置 | `server/capabilities/<instance_id>.json`(默认全栈应用)。见「配置目录」段 |
|
|
43
43
|
|
|
44
44
|
> **capabilityClient 导入警告**:`capabilityClient` 是从 `@lark-apaas/client-toolkit` 直接导入的独立对象,**不是**从 `getDataloom()` 上获取的。正确且唯一的方式:`import { capabilityClient } from '@lark-apaas/client-toolkit'`。
|
|
45
|
+
> **文件类参数**:Client 侧原始 `File` / `Blob` 直接传 `capabilityClient`。已持久化文件/图片由 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>` 是可被平台 resolver 消费的内部路径,但不是 `createSignedUrl` 的 `filePath` 入参;禁止给它拼接 `localhost`、`window.location.origin` 或 `/app/<app_id>`。非标准相对路径、前端展示 URL、非法 data URL 不作稳定入参。签名失败要 `warn`,业务状态保持 `failed` / `pending` / `null`,不写成功态。
|
|
46
|
+
> **改造已有调用**:检查所有文档和图片插件调用点,统一使用上面的签名 URL。删除下载后转 base64 的旧逻辑。缺少 `file_path` 或签名失败时记录警告,写入 `failed` / `pending` / `null`,并停止调用插件;不得改用原始 URL。
|
|
45
47
|
|
|
46
48
|
## Plugin 代码编写指南
|
|
47
49
|
|
|
@@ -567,6 +569,7 @@ npx @lark-apaas/miaoda-cli plugin list --id <instance_id>
|
|
|
567
569
|
| Mock `capabilityClient` 返回值 | 必须真实调用 |
|
|
568
570
|
| 通过 `getDataloom().capability` 调用插件 | `capabilityClient` 是独立导入 |
|
|
569
571
|
| Client 侧先通过 dataloom 上传文件再传给插件 | 直接传 File/Blob 对象 |
|
|
572
|
+
| Server 侧把非标准相对路径、误拼 origin 的 storage object path、前端展示 URL 或非法 base64 data URL 传给文件/图片参数 | 已持久化文件用 `file_path` 或 `bucket_id + file_path` 生成 1h 签名 http(s) URL 后再传;标准 storage object path 原样经过平台 resolver 时可消费,但不能直接作为 `createSignedUrl` 的 `filePath` |
|
|
570
573
|
| 用正则/字符串解析处理 AI 输出 | 用 `ai-text-to-json` / `ai-image-to-json` |
|
|
571
574
|
| 认为 `ai-doc-parser` 能直接输出结构化 JSON | 只输出纯文本,需链式调用 `ai-text-to-json` |
|
|
572
575
|
| 图片提取结构化用两步链 | 优先 `ai-image-to-json` 单步直达 |
|