@lark-apaas/coding-steering 0.1.0-alpha.1 → 0.1.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-steering",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.1",
4
4
  "description": "Stack-specific steering content for miaoda-coding templates",
5
5
  "type": "module",
6
6
  "files": ["steering"],
@@ -0,0 +1,498 @@
1
+ ---
2
+ name: plugin-guide
3
+ description: "Use when 需要:(1) 创建或管理 PluginInstance 插件实例,(2) 调用 capabilityClient 生成插件调用代码,(3) 理解 Plugin、PluginInstance、PluginInstanceAIJson 三层关系,(4) 使用 get_plugin_ai_json 或 plugin_instance 工具。触发词:插件, plugin, 飞书消息, 飞书群组, 多维表格, AI生文, AI生图, 图片理解, capabilityClient, pluginInstance"
4
+ steering: true
5
+ steering-topic: plugin_guide
6
+ ---
7
+
8
+ # Plugin 集成指南
9
+
10
+ 飞书/AI 新版插件集成规范与 Plugin 链路指南。支持的能力包括:飞书多维表格/Base操作(插入记录、更新记录、删除记录、查询记录)、发送飞书消息、创建飞书群组、AI智能生文、AI智能生图、AI图片理解等。本文档介绍插件实例配置、运行时投影(get_plugin_ai_json)的用法,以及 `capabilityClient` 调用入口;具体业务能力需以实际 available_plugin_instances 与对应 plugin.ai.json 为准。
11
+
12
+ ## ⚠️ 铁律:禁止 Mock 插件调用
13
+
14
+ **当 AGENTS.md 包含「插件规划」时,所有规划的插件必须通过 `plugin_instance` 工具创建并通过 `capabilityClient` 真实调用。**
15
+
16
+ | 禁止 | 正确 |
17
+ |------|------|
18
+ | ❌ `setTimeout` + 硬编码数据模拟 AI 返回 | ✅ `(capabilityClient as any).load(id)` → `(executor as any).call(actionKey, input)` |
19
+ | ❌ `picsum.photos` / 随机图片替代 AI 生图 | ✅ `(capabilityClient as any).load(id)` → `(executor as any).call('textToImage', {...})` |
20
+ | ❌ 注释掉 `capabilityClient` import 用 Mock 函数 | ✅ 真实 import 并调用(详见调用规范) |
21
+ | ❌ AGENTS.md 规划了插件但 `shared/capabilities/` 为空 | ✅ 每个规划的插件都有对应 JSON 文件 |
22
+ | ❌ `Math.random()` 替代 AI 分类/评分/推荐结果 | ✅ 使用插件返回值中的实际字段 |
23
+ | ❌ `logger.log(input); return { success: true }` 占位通知 | ✅ 通过 `capabilityClient` 真实调用 send-feishu-message |
24
+ | ❌ `const PRESET_DATA = [...]` 替代插件调用 | ✅ 调用插件获取真实结果 |
25
+
26
+ > **与 Mock 展示数据的区别**:design_agent prompt 中的 "Mock 数据仅用于无附件的 prototype" 指的是表格示例行、卡片占位文本等**展示数据**。AI 生文/生图/翻译/结构化等**插件能力不属于 Mock 数据范围**,必须真实调用。
27
+
28
+ ## Quick Reference
29
+
30
+ | 操作 | 方式 |
31
+ |------|------|
32
+ | 创建/更新 PluginInstance | 调用 `plugin_instance` 工具(禁止手动改文件) |
33
+ | 获取运行时投影 | 调用 `get_plugin_ai_json(pluginInstanceId)` |
34
+ | 非流式调用 | `const ex = (capabilityClient as any).load(id); await (ex as any).call(actionKey, input)` |
35
+ | 流式调用 | `const ex = (capabilityClient as any).load(id); for await (const c of (ex as any).callStream(actionKey, input)) {}` |
36
+ | capabilityClient 导入 | `import { capabilityClient } from '@lark-apaas/client-toolkit-lite'` |
37
+ | 配置存储位置 | `shared/capabilities/<plugin_instance_id>.json` |
38
+
39
+ > **capabilityClient 导入警告**:`capabilityClient` 是从 `@lark-apaas/client-toolkit-lite` 直接导入的独立对象,**不是**从 `getDataloom()` 上获取的。错误写法:`const dataloom = await getDataloom(); (dataloom as any).capability` — 这样写不会工作。正确且唯一的方式:`import { capabilityClient } from '@lark-apaas/client-toolkit-lite'`。
40
+ >
41
+ > **调用规范:双重 `as any` 强转**。lite 包暴露的 `capabilityClient` 类型是泛化合同(actionKey / input / output 都依赖运行时 `pluginInstance.ai.json`),直接调用编译器无法约束 schema。vite-react 项目里**canonical 写法是拆两步、各加一次 `as any`**:
42
+ >
43
+ > ```ts
44
+ > import { capabilityClient } from '@lark-apaas/client-toolkit-lite';
45
+ >
46
+ > const executor = (capabilityClient as any).load('ai_image_generation_quality_1');
47
+ > const result = await (executor as any).call('textToImage', {
48
+ > prompt: prompt.trim(),
49
+ > });
50
+ > ```
51
+ >
52
+ > 下方所有代码示例都遵守这个模式。链式 `(capabilityClient as any).load('id').call('action', input)` 也合法(一次 cast 让整条链路推导为 `any`),但拆步骤更便于打日志、错误处理和复用 executor。
53
+
54
+ ## Plugin 代码编写指南
55
+
56
+ 以下场景**必须**先读取 `references/plugin-coding-guide.md` 再编写代码:
57
+
58
+ - 编写 `capabilityClient` 调用代码时(含导入路径、调用方式)
59
+ - 处理流式输出(`outputMode = stream`),包括多插件并行流式、单插件 JSON 流式解析
60
+ - 需要根据 `outputMode` 选择 `call()` 或 `callStream()` 时
61
+
62
+ ## 飞书多维表格(feishu-bitable)编码指南
63
+
64
+ 以下场景**必须**先读取 `references/table.md` 再编写代码:
65
+
66
+ - 编写飞书多维表格 CRUD 代码时(searchRecords / getRecord / batchAddRecords / batchUpdateRecords / deleteRecords)
67
+ - 需要了解 BizType 字段类型、读写格式差异时
68
+ - 构建搜索过滤条件(filter / conditions)时
69
+ - 使用多维表格相关 UI 组件(人员展示、超链接、单/多选下拉 等;vite-react 用 shadcn-ui 拼装)时
70
+
71
+ ## Plugin 链式调用(Plugin Chain)
72
+
73
+ 很多业务场景需要多个插件串联完成,**禁止用正则/字符串解析替代 AI 插件做结构化输出处理**(包括提取和生成场景)。
74
+
75
+ ### 插件能力分类
76
+
77
+ | 类别 | 插件 | 输入→输出 |
78
+ |------|------|----------|
79
+ | **内容提取** | `ai-doc-parser` | 文档(PDF/DOC/PPTX/XLSX/CSV等9种)→纯文本 |
80
+ | **内容提取** | `ai-speech-to-text` | 音频→纯文本 |
81
+ | **内容提取** | `ai-image-understanding` | 图片→文本描述(流式,适合理解/问答) |
82
+ | **结构化提取** | `ai-text-to-json` | 文本→结构化 JSON(最多20字段) |
83
+ | **结构化提取** | `ai-image-to-json` | 图片→结构化 JSON(最多20字段,**单步直达**) |
84
+ | **结构化提取** | `ai-categorization` | 文本→分类标签 |
85
+ | **内容生成** | `ai-text-generate` | 提示词→文本(流式) |
86
+ | **内容生成** | `ai-text-to-image` | 文字描述→图片 |
87
+ | **内容生成** | `ai-text-summary` | 长文本→摘要(流式) |
88
+ | **内容生成** | `ai-translate` | 文本→翻译(12种语言,流式) |
89
+ | **内容生成** | `ai-search-summary` | 搜索词→网页摘要(流式) |
90
+ | **内容生成** | `ai-speech-synthesis` | 文本→语音(44+音色) |
91
+ | **图片处理** | `ai-image-matting` | 图片→抠图/去背景/去水印 |
92
+ | **图片处理** | `ai-background-replace` | 主体图+背景→合成图 |
93
+ | **图片处理** | `ai-image-to-image` | 参考图(1-5张)+描述→编辑/风格转换 |
94
+ | **图片处理** | `ai-image-compare` | 两张图→对比分析(流式) |
95
+ | **外部服务** | `feishu-bitable` | CRUD 飞书多维表格(5个Action) |
96
+ | **外部服务** | `send-feishu-message` | 发送飞书卡片消息 |
97
+ | **外部服务** | `feishu-group-create` | 创建飞书群组 |
98
+
99
+ ### 决策树:选择单步还是链式
100
+
101
+ ```
102
+ 输入是什么?
103
+ ├── 文档文件 → 必须先用 ai-doc-parser 提取文本,再根据目标选择下游插件:
104
+ │ ├── 需要结构化数据 → ai-doc-parser → ai-text-to-json (2步链)
105
+ │ ├── 需要摘要 → ai-doc-parser → ai-text-summary
106
+ │ ├── 需要翻译 → ai-doc-parser → ai-translate
107
+ │ ├── 需要分类 → ai-doc-parser → ai-categorization
108
+ │ └── 仅需原文 → ai-doc-parser(单步)
109
+ ├── 图片 → ⚠️ 注意选择正确的插件:
110
+ │ ├── 提取结构化数据(发票/名片/证件等)→ ai-image-to-json(⭐ 单步直达!)
111
+ │ ├── 理解内容后提取结构化数据 → ai-image-understanding → ai-text-to-json(2步链)
112
+ │ ├── 抠图后换背景 → ai-image-matting → ai-background-replace(2步链)
113
+ │ └── 理解/问答/编辑/对比 → 对应单插件即可
114
+ ├── 音频
115
+ │ ├── 需要结构化数据 → ai-speech-to-text → ai-text-to-json(2步链)
116
+ │ └── 仅需文字 → ai-speech-to-text(单步)
117
+ └── 纯文本
118
+ ├── 需要结构化数据 → ai-text-to-json(⭐ 单步直达!)
119
+ └── 摘要/翻译/分类/生成
120
+ ├── 输出包含多个独立字段(标题+正文+评分等)
121
+ │ → 拆成多个独立插件并行调用(⭐ 优先)或用 ai-text-to-json
122
+ │ → ⛔ 禁止用 ai-text-generate + 正则/split 解析多字段
123
+ └── 输出为单一文本(仅展示,不需解析)→ ai-text-generate
124
+ ```
125
+
126
+ ### 常见 Plugin Chain 组合
127
+
128
+ | 链路 | 插件组合 | 场景举例 |
129
+ |------|---------|---------|
130
+ | 文档→结构化数据 | `ai-doc-parser` → `ai-text-to-json` | 简历PDF→员工档案、合同→结构化条款 |
131
+ | 文档→摘要 | `ai-doc-parser` → `ai-text-summary` | 研报PDF→摘要、长文档→概要 |
132
+ | 文档→翻译 | `ai-doc-parser` → `ai-translate` | 英文论文→中文翻译 |
133
+ | 文档→分类 | `ai-doc-parser` → `ai-categorization` | 工单文档→类型标签 |
134
+ | 图片→结构化数据 | `ai-image-to-json`(**单步**) | 发票→金额/日期、名片→联系人 |
135
+ | 图片理解→结构化 | `ai-image-understanding` → `ai-text-to-json` | 复杂图表→数据、截图→字段 |
136
+ | 音频→结构化数据 | `ai-speech-to-text` → `ai-text-to-json` | 会议录音→待办事项 |
137
+ | 音频→翻译 | `ai-speech-to-text` → `ai-translate` | 外语录音→中文 |
138
+ | 抠图→换背景 | `ai-image-matting` → `ai-background-replace` | 商品图→电商主图 |
139
+ | 生成内容→通知 | `ai-text-generate` → `send-feishu-message` | AI生成报告→发送飞书通知 |
140
+ | 提取数据→入库 | `ai-text-to-json` → `feishu-bitable` | 文本→结构化→写入多维表格 |
141
+
142
+ ### Plugin Chain 调用模式
143
+
144
+ ```typescript
145
+ // 示例:文档 → 结构化数据(2步链)
146
+ // Step 1: 内容提取(注意:fileUrl 通常为数组类型,必须按 inputSchema 传入)
147
+ const docParser = (capabilityClient as any).load('doc_parser_instance');
148
+ const rawResult = await (docParser as any).call('parseDocToMarkdown', { fileUrl: [docUrl] });
149
+
150
+ // Step 2: AI 结构化提取
151
+ const jsonExtractor = (capabilityClient as any).load('text_to_json_instance');
152
+ const structured = await (jsonExtractor as any).call('textToJson', { text: rawResult.content });
153
+
154
+ // Step 3: 使用结果(填入表单 / 写入数据库 / 写入多维表格等)
155
+ ```
156
+
157
+ > **Client 侧提示**:`capabilityClient` 支持直接传 File/Blob 对象作为文件参数,无需先上传到 dataloom 获取 URL:
158
+ >
159
+ > ```typescript
160
+ > // Client 侧:直接传 File 对象,SDK 自动处理上传
161
+ > const docParser = (capabilityClient as any).load('doc_parser_instance');
162
+ > const rawResult = await (docParser as any).call('parseDocToMarkdown', { fileUrl: [file] }); // file 为 File/Blob 对象
163
+ > ```
164
+ >
165
+ > ⚠️ `capabilityClient` 支持此能力,SDK 自动处理文件上传。
166
+
167
+ ### 创建结构化提取 PluginInstance 的关键要求
168
+
169
+ 创建 `ai-text-to-json` 或 `ai-image-to-json` 类型的 PluginInstance 时:
170
+
171
+ 1. **必须一次性定义所有需要提取的字段**(参考数据库 schema / 表单定义 / UI 设计),宁多勿漏
172
+ 2. 字段类型仅支持 String / Number / Boolean,最多 20 个字段
173
+ 3. 先调用 `get_plugin_ai_json` 确认上游插件的 `outputSchema`,确保输入格式正确
174
+ 4. 图片→结构化数据场景,优先使用 `ai-image-to-json`(单步),避免不必要的链式调用
175
+
176
+ ## 核心概念
177
+
178
+ 新版链路中,**Plugin(插件)**、**PluginInstance(插件实例配置)**、**PluginInstanceAIJson(运行时投影:pluginInstance.ai.json)** 的关系如下:
179
+
180
+ - **Plugin(插件)**:底层承载单元,包含插件元信息与表单定义(form.schema)。模型侧只感知插件及其表单字段,不感知插件内部实现细节。
181
+ - **PluginInstance(插件实例配置)**:基于某个 Plugin 的表单做"业务封装",以 **单文件 JSON** 的形式存储(每个插件实例一个文件,语义化 id)。
182
+ - 通过 `paramsSchema` 暴露业务入参
183
+ - 通过 `formValue` 将业务入参映射到插件表单字段(可常量或引用 `{{input.xxx}}`)
184
+ - **PluginInstanceAIJson(pluginInstance.ai.json)**:工程转化层产物,是 pluginInstance 的**运行时投影 / 调用合同(Runtime Spec)**。
185
+ - 包含插件定位信息、actions 入口列表、input/output schema、outputMode、readme 等
186
+ - Code Agent 在生成**调用代码**前,必须读取它作为权威依据(使用 `capabilityClient` 调用)
187
+
188
+ ### 插件 Plugin
189
+
190
+ 插件是插件实例的承载单元,包含:
191
+ • 插件元信息(tags/name/description/version/...)
192
+ • 插件表单定义(form.schema),用于描述"这个插件需要哪些表单字段"。
193
+ 重要:模型侧只感知插件与其表单 schema,不感知插件内部实现(如 Action的实现、API 细节等)。
194
+
195
+ Plugin 的具体内容以JSON格式给出,例如:
196
+
197
+ ```json
198
+ {
199
+ "name": "plugin-key", // 插件唯一标识
200
+ "displayName": "飞书群组创建", // 展示名称
201
+ "version": "1.0.0",
202
+ "form": {
203
+ "schema": { // 插件表单定义(PluginInstance 的 formValue 映射到此)
204
+ "type": "object",
205
+ "properties": {
206
+ "group_name": { "type": "string", "description": "群组名称" },
207
+ "members": { "type": "array", "description": "群成员id列表", "items": { "type": "string" } }
208
+ },
209
+ "required": ["group_name", "members"]
210
+ }
211
+ }
212
+ }
213
+ ```
214
+
215
+ ### 插件实例 PluginInstance
216
+
217
+ 开发框架内置 `plugin` 工具,用于创建和管理基于 **Plugin(插件表单)** 的业务插件实例(PluginInstance)。
218
+
219
+ **重要说明**:
220
+
221
+ - PluginInstance 的配置以"单文件 JSON"形式存储在 `shared/capabilities/`(每个插件实例一个文件,逻辑上对应 shared/capabilities/<id>.json)。
222
+ - 运行时调用前,Code Agent 需要通过 get_plugin_ai_json 获取对应插件实例的 pluginInstance.ai.json,再基于其中的 actions/schema/outputMode 生成调用代码。
223
+ - 运行时调用入口统一走 SDK:`(capabilityClient as any).load(pluginInstanceId)` 拿到 executor,再 `(executor as any).call(actionKey, input)`(流式用 `callStream`,详见 SKILL.md 头部"调用规范")
224
+
225
+ PluginInstance 的配置以 JSON 形式输出,例如:
226
+
227
+ ```json
228
+ {
229
+ "id": "create_feishu_group", // 全局唯一语义化 ID
230
+ "pluginKey": "@xxx/feishu-group", // 绑定的 Plugin name
231
+ "pluginVersion": "1.0.0",
232
+ "name": "任务创建时自动创建飞书群组",
233
+ "description": "根据任务名称自动生成飞书群组并设置初始成员",
234
+ "paramsSchema": { // 对外暴露的业务入参(仅 string / array<string> / picture / file)
235
+ "type": "object",
236
+ "properties": {
237
+ "group_name": { "type": "string", "description": "群组名称" },
238
+ "members": { "type": "array", "items": { "type": "string" }, "description": "成员ID列表" }
239
+ },
240
+ "required": ["group_name"]
241
+ },
242
+ "formValue": { // 映射到 Plugin form.schema 字段(常量或 {{input.xxx}})
243
+ "group_name": "{{input.group_name}}",
244
+ "members": "{{input.members}}"
245
+ }
246
+ }
247
+ ```
248
+
249
+ **注意**paramsSchema 支持以下 4 种参数类型,需要按下面规定的格式进行填充:
250
+
251
+ 1. **文本** - 单行或多行文本输入
252
+
253
+ ```json
254
+ {
255
+ "type": "string",
256
+ "description": "文本参数描述"
257
+ }
258
+ ```
259
+
260
+ 2. **数组** - 字符串数组(如 ID 列表、标签列表等)
261
+
262
+ ```json
263
+ {
264
+ "type": "array",
265
+ "description": "数组参数描述",
266
+ "items": {
267
+ "type": "string",
268
+ "description": "数组元素描述"
269
+ }
270
+ }
271
+ ```
272
+
273
+ 3. **图片** - 图片资源(需指定 format 为 picture)
274
+
275
+ ```json
276
+ {
277
+ "type": "string",
278
+ "format": "picture",
279
+ "description": "图片参数描述"
280
+ }
281
+ ```
282
+
283
+ 4. **文件** - 文件资源(需指定 format 为 file)
284
+
285
+ ```json
286
+ {
287
+ "type": "string",
288
+ "format": "file",
289
+ "description": "文件参数描述"
290
+ }
291
+ ```
292
+
293
+ > **注意**:`format: "file"` 和 `format: "picture"` 的字段支持直接传入 File/Blob 对象,`capabilityClient` SDK 会自动处理上传。
294
+ >
295
+ #### PluginInstanceAIJson(运行时投影 / 工程转化层产物:pluginInstance.ai.json)
296
+
297
+ pluginInstance.ai.json 是从 PluginInstance 配置派生出的运行时插件实例说明(Runtime Spec),用于 Code Agent 动态生成调用代码。
298
+ 它包含:
299
+ • 插件实例元数据(id/pluginKey/pluginVersion/name/description)
300
+ • 可执行入口列表 actions[](每个入口包含 key/inputSchema/outputSchema/outputMode)
301
+ • 详细说明 readme
302
+ • type:单入口/多入口(single_action | multi_action)
303
+
304
+ **重要**:模型不能自行猜测某个插件实例有哪些 action、入参/出参结构;在生成调用代码前必须通过工具读取该 pluginInstance.ai.json。
305
+
306
+ PluginInstanceAIJson 的配置以 JSON 形式输出,例如:
307
+
308
+ ```json
309
+ {
310
+ "type": "multi_action", // 插件实例类型:single_action 表示仅 1 个 action;multi_action 表示多个 action(调用前需选择 actionKey)
311
+ "id": "********", // PluginInstance 的唯一标识(插件实例ID),用于后续通过 get_plugin_ai_json 获取详情、以及运行时调用定位插件实例
312
+ "pluginKey": "@*******", // 该插件实例绑定的插件ID(运行时用于定位具体插件实现)
313
+ "pluginVersion": "1.0.0", // 插件版本号(用于版本锁定/兼容性,避免插件升级导致 schema 或行为变化)
314
+ "name": "******", // 插件实例名称(面向人/模型展示,用于检索与选择插件实例)
315
+ "description": "...", // 插件实例描述(说明该插件实例解决什么业务问题、适用场景,用于帮助模型理解意图)
316
+ "actions": [ // 可执行入口列表
317
+ {
318
+ "key": "insertRecords", // action 标识(调用时作为 actionKey 使用):插入记录/新增数据
319
+ "inputSchema": {}, // 该 action 的入参 JSON Schema(生成调用参数时必须严格遵循)
320
+ "outputSchema": {}, // 该 action 的出参 JSON Schema(解析返回值时按此结构读取字段)
321
+ "outputMode": "unary" // 输出模式:unary=一次性返回;stream=流式返回(决定代码生成的处理方式)
322
+ },
323
+ {
324
+ "key": "updateRecords", // action 标识:更新记录/修改数据
325
+ "inputSchema": {}, // 更新操作需要的入参结构定义(JSON Schema)
326
+ "outputSchema": {}, // 更新操作返回结构定义(JSON Schema)
327
+ "outputMode": "unary" // 输出模式(同上)
328
+ },
329
+ {
330
+ "key": "deleteRecord", // action 标识:删除记录(注意:有的插件实例会是 deleteRecords 表示批量删除)
331
+ "inputSchema": {}, // 删除操作需要的入参结构定义(JSON Schema)
332
+ "outputSchema": {}, // 删除操作返回结构定义(JSON Schema)
333
+ "outputMode": "unary" // 输出模式(同上)
334
+ }
335
+ ],
336
+ "readme": "", // 插件实例使用说明文档(可能包含特殊字段解释、限制、示例代码;优先参考它来生成调用逻辑)
337
+ "createdAt": 1764234360374, // 插件实例创建时间戳(毫秒),用于变更追踪/缓存刷新
338
+ "updatedAt": 1764234360374 // 插件实例更新时间戳(毫秒),用于变更追踪/缓存刷新
339
+ }
340
+ ```
341
+
342
+ ## 可用的 Plugin
343
+
344
+ ```
345
+ {{available_plugins}}
346
+ ```
347
+
348
+ 说明:先从可用的 PluginInstance 进行选择,如果无法满足需求,看可用的 Plugin,如果有满足的插件,调用插件生成工具进行生成,如果没有,直接拒答。
349
+
350
+ ## 可用的 PluginInstance
351
+
352
+ ```
353
+ {{available_plugin_instances}}
354
+ ```
355
+
356
+ ### 使用方式
357
+
358
+ 1. **创建/修改 PluginInstance(配置层)**:调用 `plugin_instance` 工具生成/更新单文件 PluginInstance JSON(基于插件表单封装)。
359
+ 2. **查询已有 PluginInstance(配置层)**:优先使用可用的 PluginInstance;如仍需核对细节,再读取对应插件实例配置(调用'get_plugin_ai_json')。
360
+ 3. **生成运行时代码(调用层)**:
361
+ - 已确定使用某个 PluginInstance 后,先调用 `get_plugin_ai_json` 获取该插件实例的运行时投影(pluginInstance.ai.json)
362
+ - 基于返回的 `actions[].key/inputSchema/outputSchema/outputMode` 动态生成调用代码(使用 `capabilityClient`)
363
+ - 仔细阅读返回的 readme,必须严格遵循里面制定的规则
364
+
365
+ ### 典型场景示例
366
+
367
+ - **消息通知类**:封装"发送飞书消息"相关插件为业务插件实例
368
+ - **群组管理类**:封装"创建飞书群组"相关插件为业务插件实例
369
+ - **AI 生成类**:封装"AI 生文/生图/图片理解"相关插件为业务插件实例
370
+
371
+ ### 使用限制
372
+
373
+ - PluginInstance 必须通过 `plugin_instance` 工具创建/更新,不支持 agent 直接手改 `shared/capabilities/` 下的配置文件
374
+ - 调用前必须通过 `get_plugin_ai_json` 获取权威 schema,禁止猜测入参/出参结构
375
+ - PluginInstance 配置信息存储在 `shared/capabilities/` 目录
376
+ - 运行时调用统一走 SDK,不再为每个插件实例预生成固定的 call 文件:`(capabilityClient as any).load(capabilityId)` → `(executor as any).call(actionKey, input)`(流式用 `callStream`)
377
+
378
+ ## PluginInstance 生成约束
379
+
380
+ 1. 非常**注意**,必须通过调用 `plugin_instance`工具 来创建、更新 PluginInstance,**绝对禁止**使用 `multi_edit`工具 和 `write`工具 来直接修改 `shared/capabilities/` 目录下的内容。
381
+ 2. PluginInstance 配置以单文件形式存储在 shared/capabilities/ 目录下(逻辑上为 shared/capabilities/<plugin_instance_id>.json),不再维护集中式的 capabilities.json。
382
+ 3. 用户可以手动修改 `shared/capabilities/<plugin_instance_id>.json` 文件中的配置,且用户修改完配置后一定会通知你(例如告诉你'刚刚更新了PluginInstance配置'),此时你需要根据用户修改后的配置,通过调用(必须,禁止使用其他工具来操作 PluginInstance)`plugin_instance`工具 来更新PluginInstance。
383
+ 4. 调用侧在生成调用代码前,**必须**通过 get_plugin_ai_json 获取该插件实例的运行时投影(plugin_Instance.ai.json),并以其作为入参/出参 schema 与 action 列表的唯一权威依据,禁止自行猜测。
384
+ 5. 若插件表单字段中包含 card_content 且其语义为飞书消息卡片,则生成/更新 PluginInstance 配置时,formValue.card_content 必须是 JSON Object,不能是转义字符串;并且卡片 DSL 的最后两个元素必须固定。
385
+
386
+ ---
387
+
388
+ ## GetPluginInstanceAIJson 工具使用指南
389
+
390
+ `get_plugin_ai_json(pluginInstanceID)` — 读取插件实例的运行时投影,返回结构参见上文 PluginInstanceAIJson 示例。
391
+
392
+ ### 何时调用
393
+
394
+ | 场景 | 是否调用 |
395
+ |------|---------|
396
+ | 已选定插件实例,需生成调用代码 | **必须调用** |
397
+ | 需确认 actions / inputSchema / outputSchema / outputMode | **必须调用** |
398
+ | 创建或修改 PluginInstance 配置 | 不需要(用 `plugin_instance` 工具) |
399
+ | 只需插件实例列表概览 | 不需要(已在上下文中提供) |
400
+
401
+ ### 消费返回数据要点
402
+
403
+ 1. 根据 `actions[].key` 选择 actionKey,严格按 `inputSchema` 构造入参、按 `outputSchema` 解析出参
404
+ 2. `type = single_action` 时只有一个 action;`multi_action` 时需选择合适的 actionKey
405
+ 3. **务必阅读 `readme` 字段**,可能包含特殊参数说明、使用限制和示例代码
406
+ 4. 注意 `inputSchema` 中的类型定义(特别是 `type: array` 的字段,必须传数组而非字符串)
407
+
408
+ ## 开发流程要求
409
+
410
+ ### 第一步:检查现有 PluginInstance(复用优先)
411
+
412
+ 1. 用户描述需求后,优先基于上下文提供的插件实例列表检索是否已存在可复用插件实例。
413
+ 1. 若存在候选插件实例但你无法确认其是否满足需求,必须调用 `get_plugin_ai_json` 获取该插件实例的运行时投影(pluginInstance.ai.json),根据 `actions[].key` / `actions[].inputSchema / outputSchema` / `actions[].outputMode` 判断是否可复用以及如何调用。
414
+ 1. 禁止按旧链路去读取/维护 `shared/capabilities/capabilities.json` 来做复用判断。
415
+
416
+ > 结论:**复用判断以插件实例列表 + get_plugin_ai_json 为准**,禁止猜测 action、入参/出参、输出模式。
417
+
418
+ ### 第二步:决策
419
+
420
+ - 如果找到匹配的插件实例:直接进入「第三步:代码调用」,严禁为了"更贴合需求"而随意新建重复的插件实例。
421
+ - 如果不存在合适插件实例,则基于上下文提供的插件列表进行判断:
422
+ - 需要创建:如果插件列表有更贴合需求的插件,调用 `plugin_instance` 工具(operType=CREATE)生成新的单文件 PluginInstance 配置。
423
+ - 需要调整已有插件实例:调用 `plugin_instance` 工具(operType=UPDATE)更新该插件实例配置。
424
+ - 其他情况:告知用户该需求目前无法满足
425
+
426
+ **强制约束**:
427
+
428
+ - 创建/更新 PluginInstance **必须**通过 `plugin_instance` 工具完成,绝对禁止直接修改 `shared/capabilities/` 下的配置文件。
429
+ - UPDATE 场景严禁修改保护字段:`id / pluginKey / pluginVersion / createdAt`。
430
+
431
+ ### 第三步:生成调用代码
432
+
433
+ 1. **必须**先调用 `get_plugin_ai_json(pluginInstanceId)`
434
+ 2. 根据 `actions[].key` 选择正确的 actionKey
435
+ 3. 严格按 `inputSchema` 构造入参,严格按 `outputSchema` 解析出参
436
+ 4. 根据 `outputMode` 选择调用方式:
437
+ - `outputMode = unary` → `(executor as any).call(actionKey, input)`
438
+ - `outputMode = stream` → `(executor as any).callStream(actionKey, input)`(异步迭代)
439
+
440
+ ### 第四步:代码放置位置
441
+
442
+ 调用代码放在 `src/` 目录下的组件/hooks 中(vite-react flat 布局),由用户交互触发。
443
+
444
+ ## 常见错误(必须避免)
445
+
446
+ | 错误做法 | 正确做法 |
447
+ |---------|---------|
448
+ | 为插件调用创建后端 API 中转 | 直接使用 `capabilityClient` 在前端调用 |
449
+ | 未调用 `get_plugin_ai_json` 就猜测参数 | 先获取 runtime spec,再生成代码 |
450
+ | Mock `capabilityClient` 返回值 | 必须真实调用 |
451
+ | 猜测 actionKey 或参数结构 | 严格按 `get_plugin_ai_json` 返回的 schema |
452
+ | `call()` 调用签名错误:`plugin.call(JSON.stringify({...}))` 或 `plugin.call({...})` | `call()` 第一个参数必须是 actionKey 字符串,第二个参数才是 input 对象:`plugin.call('actionKey', {...})` |
453
+ | 用正则/字符串解析处理 AI 输出(提取或生成场景) | 提取用 `ai-text-to-json` / `ai-image-to-json`;多字段生成拆多插件或用 `ai-text-to-json` |
454
+ | 创建 `ai-text-to-json` / `ai-image-to-json` PluginInstance 时只定义部分字段 | 分析需求中**全部**字段后一次性定义完整(最多20字段) |
455
+ | 认为 `ai-doc-parser` 能直接输出结构化 JSON | `ai-doc-parser` 只输出纯文本,需链式调用 `ai-text-to-json` 做结构化 |
456
+ | 图片提取结构化数据时用 `ai-image-understanding` → `ai-text-to-json` 两步链 | 优先用 `ai-image-to-json` 单步直达(发票/名片/证件等场景) |
457
+ | 未读 `inputSchema` 就假设参数类型(如 string vs array) | 先调用 `get_plugin_ai_json` 查看 `inputSchema`,注意 `type: array` 字段 |
458
+ | 通过 `getDataloom().capability` 或 `(dataloom as any).capability` 调用插件 | `capabilityClient` 是独立导入,不通过 dataloom 访问。dataloom 仅提供 storage 和 service |
459
+ | 调用插件时,先通过 dataloom 上传文件拿 URL 再传给插件 | 可直接传 File/Blob 对象给 `capabilityClient`,SDK 自动处理上传 |
460
+ | 前端调用插件后不保存结果到数据库,导致页面刷新后数据丢失 | 调用插件成功后,必须通过已有 CRUD 接口立即保存结果 |
461
+ | 为保存插件结果单独新建 API 端点(如 `PATCH /api/xxx/ai-analysis`) | 优先复用已有的业务 CRUD 接口(create/update)扩展字段 |
462
+
463
+ ## 业务语义映射约定
464
+
465
+ 用户会用「AI 生文」「AI 生图」「发送飞书消息」等**业务语言**描述需求;这些关键词必须被识别为**待使用或待创建的 PluginInstance**。
466
+
467
+ 开发流程:
468
+
469
+ 1. 收到需求后,先看可用的 PluginInstance 是否有可以直接使用的插件实例
470
+ 2. 若有候选但不确定是否满足,调用 `get_plugin_ai_json` 查看其 actions/schema/outputMode 再决策
471
+ 3. 若无,立即调用 `plugin_instance` 工具新建
472
+ 4. 生成 `capabilityClient` 调用代码
473
+
474
+ ## 插件调用错误处理规范
475
+
476
+ | 错误类型 | 应对策略 |
477
+ |----------|---------|
478
+ | `InputValidationError` | 修复参数后重试 |
479
+ | `RateLimitError` | 指数退避重试(1s/2s/4s),最多 3 次 |
480
+ | `ExecutionError` | 记录日志 + 降级方案 + 通知用户 |
481
+ | 网络超时 | 重试 + 超时后降级 |
482
+
483
+ **规则**: (1) 禁止静默吞异常,每个 catch 必须向用户展示错误或触发补偿;(2) 异步调用必须有终态(success/failed),前端不能永远 loading;(3) 通知类插件失败必须有补偿机制。
484
+
485
+ ## 缓存与幂等性
486
+
487
+ - AI 类插件**没有请求级缓存**。同样输入返回相似结果是 LLM 低 temperature 下的正常行为。
488
+ - **禁止**通过修改业务参数注入 UUID 来"绕缓存",会污染 AI 输入导致输出包含垃圾文本。
489
+ - 需要不同结果时:调整 temperature、修改 prompt 要求、或使用不同业务上下文。
490
+
491
+ ## 插件参数来源规范
492
+
493
+ | 类型 | 来源 | 示例 |
494
+ |------|------|------|
495
+ | 业务数据 | DB 查询或前端传入 | 候选人姓名、简历内容 |
496
+ | 运行时配置 | 配置/环境变量/平台 API | 接收人 user_id、阈值 |
497
+
498
+ **禁止**硬编码运行时配置值(user_id、邮箱等),必须从配置表/平台 API/环境变量获取。
@@ -0,0 +1,143 @@
1
+ ## PluginInstance 代码编写指南
2
+
3
+ ### 核心原则
4
+
5
+ **严禁** import { capabilityClient } from '@lark-apaas/client-capability'。
6
+ **唯一指定**的导入方式是 import { capabilityClient } from '@lark-apaas/client-toolkit-lite';
7
+
8
+ | 场景 | 调用方式 |
9
+ |------|---------|
10
+ | 非流式调用 | `const ex = (capabilityClient as any).load(id); await (ex as any).call(actionKey, input)` |
11
+ | 流式输出场景 | `const ex = (capabilityClient as any).load(id); for await (const c of (ex as any).callStream(actionKey, input)) {}` |
12
+
13
+ ---
14
+
15
+ ### 调用方式
16
+
17
+ #### 1. 调用前获取权威依据
18
+
19
+ 在为某个插件实例生成调用代码前,必须先通过 `get_plugin_ai_json` 工具获取该插件实例的运行时投影(plugin_Instance.ai.json),并以其中信息为准:
20
+
21
+ - `actions[].key`:调用时要传的 `actionKey`
22
+ - `actions[].inputSchema / outputSchema`:入参/出参结构
23
+ - `actions[].outputMode`:`unary | stream`(决定调用与结果处理方式)
24
+
25
+ #### call / callStream 函数签名
26
+
27
+ ```typescript
28
+ .call(actionKey: string, input: object) // 非流式,返回 Promise<output>
29
+ .callStream(actionKey: string, input: object) // 流式,返回 AsyncIterable<chunk>
30
+ ```
31
+
32
+ - **第一个参数 `actionKey`**:必须是字符串,值来自 `get_plugin_ai_json` 返回的 `actions[].key`(如 `'sendFeishuMessage'`、`'textGenerate'`)
33
+ - **第二个参数 `input`**:必须是对象,结构符合 `actions[].inputSchema`
34
+
35
+ vite-react 侧 TS 类型偏泛,调用必须 `as any` 强转两次(一次给 `capabilityClient`,一次给 `load` 返回的 executor):
36
+
37
+ ```typescript
38
+ import { capabilityClient } from '@lark-apaas/client-toolkit-lite';
39
+
40
+ const executor = (capabilityClient as any).load('plugin_instance_id');
41
+
42
+ // ❌ 错误:把参数 JSON.stringify 后当作 actionKey
43
+ (executor as any).call(JSON.stringify({ meeting_title: '...' }));
44
+ // ❌ 错误:漏掉 actionKey,直接传参数对象
45
+ (executor as any).call({ meeting_title: '...' });
46
+
47
+ // ✅ 正确:第一个参数是 actionKey 字符串,第二个参数是 input 对象
48
+ const result = await (executor as any).call('send_feishu_message', { meeting_title: '...' });
49
+ ```
50
+
51
+ #### 2. 非流式调用(outputMode = "unary")
52
+
53
+ ```typescript
54
+ import { capabilityClient, logger } from '@lark-apaas/client-toolkit-lite';
55
+
56
+ const executor = (capabilityClient as any).load('create_feishu_group');
57
+ const result = await (executor as any).call('createGroup', {
58
+ group_name: '项目讨论群',
59
+ members: ['user_001', 'user_002'],
60
+ });
61
+
62
+ logger.info(result);
63
+ ```
64
+
65
+ #### 3. 流式调用(outputMode = "stream")
66
+
67
+ ##### 流式 chunk 字段速查表(必须遵守)
68
+
69
+ capabilityClient `callStream` 返回的每个 chunk 是**扁平对象**,字段名与 `outputSchema` 一致。**禁止使用 `chunk.data?.text`、`chunk.choices[0]`、`chunk.message` 等非 capabilityClient 格式。**
70
+
71
+ | 插件 | chunk 字段 | 正确写法 | 错误写法 |
72
+ |------|-----------|---------|---------|
73
+ | ai-text-generate | `content` | `chunk.content` | ~~`chunk.data?.text`~~ |
74
+ | ai-translate | `translation` | `chunk.translation` | ~~`chunk.content`~~ ~~`chunk.text`~~ |
75
+ | ai-text-summary | `summary` | `chunk.summary` | ~~`chunk.content`~~ ~~`chunk.data?.text`~~ |
76
+ | ai-image-understanding | `content` | `chunk.content` | ~~`chunk.data?.text`~~ |
77
+ | ai-search-summary | `content` | `chunk.content` | ~~`chunk.data?.text`~~ |
78
+
79
+ > **⚠️ 同一应用多个页面调用同一插件时,所有页面必须使用一致的 chunk 字段名。** parallel_write 并发生成多页面时尤其注意。
80
+
81
+ ##### 场景判断与推荐方案
82
+
83
+ | 场景 | 特征 | 推荐度 |
84
+ |-----|------|-------|
85
+ | **多插件并行流式** | 多个插件各返回单一输出,并行调用 | **优先推荐** |
86
+ | **单插件 JSON 流式解析** | 单插件返回结构化 JSON,需边接收边解析 | ⚠️ 仅在必要时 |
87
+
88
+ **核心原则**:在插件设计阶段按「原子化拆解」拆分,避免单插件返回多字段 JSON。
89
+
90
+ ##### 推荐:多插件并行流式
91
+
92
+ 适用于需求涉及多种输出(标题、正文、图片等),各输出相对独立。
93
+
94
+ ```tsx
95
+ import { capabilityClient, logger } from '@lark-apaas/client-toolkit-lite';
96
+
97
+ function MultiPluginStreamExample() {
98
+ const [title, setTitle] = useState('');
99
+ const [content, setContent] = useState('');
100
+ const [coverUrl, setCoverUrl] = useState('');
101
+
102
+ const handleGenerate = async (keywords: string) => {
103
+ // 1. 封面图(非流式,异步不阻塞)
104
+ const coverExecutor = (capabilityClient as any).load('cover_generator');
105
+ (coverExecutor as any).call('textToImage', { keywords })
106
+ .then((res: { images?: string[] }) => res?.images?.[0] && setCoverUrl(res.images[0]))
107
+ .catch((err: unknown) => logger.warn('封面生成失败', err));
108
+
109
+ // 2. 标题(非流式)
110
+ const titleExecutor = (capabilityClient as any).load('title_generator');
111
+ (titleExecutor as any).call('textGenerate', { keywords })
112
+ .then((res: { content?: string }) => res?.content && setTitle(res.content));
113
+
114
+ // 3. 正文(流式,边生成边展示)
115
+ const contentExecutor = (capabilityClient as any).load('content_generator');
116
+ const contentStream = (contentExecutor as any).callStream('textGenerate', { keywords });
117
+
118
+ // 🎯 流式内容直接拼接,无需复杂解析
119
+ for await (const chunk of contentStream as AsyncIterable<{ content?: string }>) {
120
+ setContent(prev => prev + (chunk.content || ''));
121
+ }
122
+ };
123
+
124
+ return (/* 各字段独立渲染 */);
125
+ }
126
+ ```
127
+
128
+ **优点**:代码简洁、各插件独立、某个失败不影响其他。
129
+
130
+ ##### ⚠️ 兜底:单插件 JSON 流式解析
131
+
132
+ 当无法拆分为多插件时,需处理不完整 JSON 的逐字符到达,需实现 `parseStreamingStringField` 和 `extractJsonObject` 工具函数。**强烈建议在插件设计阶段拆分为多插件并行流式调用,避免此场景。**
133
+
134
+ ### outputMode 与调用方式
135
+
136
+ 先通过 `get_plugin_ai_json(pluginInstanceId)` 获取 `actions[].outputMode`:
137
+
138
+ | outputMode | 调用方式 |
139
+ |------------|---------|
140
+ | `unary` | `const ex = (capabilityClient as any).load(id); await (ex as any).call(actionKey, input)` |
141
+ | `stream` | `const ex = (capabilityClient as any).load(id); for await (const c of (ex as any).callStream(actionKey, input)) {}` |
142
+
143
+ ---