@downcity/agent 1.1.152 → 1.1.157
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/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -1
- package/bin/agent/local/services/AgentAssemblyService.js +1 -0
- package/bin/agent/local/services/AgentAssemblyService.js.map +1 -1
- package/bin/executor/composer/system/default/assets/plugin.prompt.d.ts +1 -1
- package/bin/executor/composer/system/default/assets/plugin.prompt.d.ts.map +1 -1
- package/bin/executor/composer/system/default/assets/plugin.prompt.js +1 -1
- package/bin/executor/composer/system/default/assets/plugin.prompt.js.map +1 -1
- package/bin/executor/tools/plugin/PluginToolBridge.d.ts +6 -2
- package/bin/executor/tools/plugin/PluginToolBridge.d.ts.map +1 -1
- package/bin/executor/tools/plugin/PluginToolBridge.js +71 -17
- package/bin/executor/tools/plugin/PluginToolBridge.js.map +1 -1
- package/bin/executor/tools/plugin/PluginToolDefinition.d.ts +11 -0
- package/bin/executor/tools/plugin/PluginToolDefinition.d.ts.map +1 -1
- package/bin/executor/tools/plugin/PluginToolDefinition.js +12 -3
- package/bin/executor/tools/plugin/PluginToolDefinition.js.map +1 -1
- package/bin/executor/tools/plugin/PluginToolSchemas.d.ts +4 -0
- package/bin/executor/tools/plugin/PluginToolSchemas.d.ts.map +1 -1
- package/bin/executor/tools/plugin/PluginToolSchemas.js +11 -1
- package/bin/executor/tools/plugin/PluginToolSchemas.js.map +1 -1
- package/bin/executor/tools/plugin/types/PluginTool.d.ts +37 -0
- package/bin/executor/tools/plugin/types/PluginTool.d.ts.map +1 -1
- package/bin/index.d.ts +3 -1
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +1 -0
- package/bin/index.js.map +1 -1
- package/bin/plugin/core/PluginActionFactory.d.ts +99 -0
- package/bin/plugin/core/PluginActionFactory.d.ts.map +1 -0
- package/bin/plugin/core/PluginActionFactory.js +59 -0
- package/bin/plugin/core/PluginActionFactory.js.map +1 -0
- package/bin/plugin/core/PluginActionRunner.d.ts.map +1 -1
- package/bin/plugin/core/PluginActionRunner.js +14 -1
- package/bin/plugin/core/PluginActionRunner.js.map +1 -1
- package/bin/plugin/core/PluginLocalExecution.d.ts.map +1 -1
- package/bin/plugin/core/PluginLocalExecution.js +15 -1
- package/bin/plugin/core/PluginLocalExecution.js.map +1 -1
- package/bin/plugin/core/PluginRegistry.d.ts +18 -1
- package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
- package/bin/plugin/core/PluginRegistry.js +74 -1
- package/bin/plugin/core/PluginRegistry.js.map +1 -1
- package/bin/plugin/types/Plugin.d.ts +2 -2
- package/bin/plugin/types/Plugin.d.ts.map +1 -1
- package/bin/types/plugin/PluginAction.d.ts +40 -2
- package/bin/types/plugin/PluginAction.d.ts.map +1 -1
- package/bin/types/plugin/PluginRuntime.d.ts +44 -0
- package/bin/types/plugin/PluginRuntime.d.ts.map +1 -1
- package/package.json +3 -3
- package/scripts/image-plugin-job.test.mjs +308 -32
- package/scripts/plugin-tool-bridge.test.mjs +199 -0
- package/src/agent/local/services/AgentAssemblyService.ts +1 -0
- package/src/executor/composer/system/default/assets/plugin.prompt.ts +1 -1
- package/src/executor/composer/system/default/assets/plugin.prompt.ts.txt +3 -2
- package/src/executor/tools/plugin/PluginToolBridge.ts +84 -18
- package/src/executor/tools/plugin/PluginToolDefinition.ts +25 -4
- package/src/executor/tools/plugin/PluginToolSchemas.ts +12 -1
- package/src/executor/tools/plugin/types/PluginTool.ts +40 -0
- package/src/index.ts +11 -0
- package/src/plugin/core/PluginActionFactory.ts +185 -0
- package/src/plugin/core/PluginActionRunner.ts +14 -1
- package/src/plugin/core/PluginLocalExecution.ts +15 -1
- package/src/plugin/core/PluginRegistry.ts +89 -1
- package/src/plugin/types/Plugin.ts +5 -0
- package/src/types/plugin/PluginAction.ts +43 -2
- package/src/types/plugin/PluginRuntime.ts +44 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -4,15 +4,19 @@
|
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - tool 层不直接持有 agent 或 plugin registry,只通过装配期注入的 AgentPlugins 调用 action。
|
|
6
6
|
* - 如果 action 返回 AI SDK UIMessage,则抽取 file parts 并入最终 assistant 消息。
|
|
7
|
-
* - 返回给模型的 tool result
|
|
7
|
+
* - 返回给模型的 tool result 只保留短摘要和本地绝对路径,避免 data URL 等大内容污染上下文。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import path from "node:path";
|
|
10
11
|
import type { FileUIPart } from "ai";
|
|
11
12
|
import type { JsonObject, JsonValue } from "@/types/common/Json.js";
|
|
12
13
|
import type { AgentPlugins } from "@/plugin/types/Plugin.js";
|
|
13
14
|
import type {
|
|
14
15
|
PluginCallInput,
|
|
16
|
+
PluginCallToolFileResult,
|
|
15
17
|
PluginCallToolResult,
|
|
18
|
+
PluginReadInput,
|
|
19
|
+
PluginReadToolResult,
|
|
16
20
|
} from "@/executor/tools/plugin/types/PluginTool.js";
|
|
17
21
|
import { materializeAssistantFileParts } from "@executor/messages/AssistantFileResource.js";
|
|
18
22
|
import {
|
|
@@ -69,27 +73,57 @@ function extract_assistant_file_parts(data: JsonValue | undefined): FileUIPart[]
|
|
|
69
73
|
return parts.filter(is_file_part);
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
/**
|
|
77
|
+
* 解析当前项目根目录,保持与 assistant 文件落盘逻辑一致。
|
|
78
|
+
*/
|
|
79
|
+
function resolve_project_root(project_root: string | undefined): string {
|
|
80
|
+
const raw = String(project_root || "").trim();
|
|
81
|
+
return path.resolve(raw || process.cwd());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 将 `resources://` URL 转成本机绝对路径。
|
|
86
|
+
*/
|
|
87
|
+
function resolve_resources_file_path(
|
|
88
|
+
project_root: string,
|
|
89
|
+
raw_url: string,
|
|
90
|
+
): string {
|
|
91
|
+
const prefix = "resources://";
|
|
92
|
+
const raw = String(raw_url || "").trim();
|
|
93
|
+
if (!raw.startsWith(prefix)) return "";
|
|
94
|
+
const relative = raw.slice(prefix.length).replace(/^\/+/, "");
|
|
95
|
+
if (!relative) return "";
|
|
96
|
+
|
|
97
|
+
const file_path = path.resolve(project_root, relative);
|
|
98
|
+
const rel = path.relative(project_root, file_path);
|
|
99
|
+
if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) return "";
|
|
100
|
+
return file_path;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 构建返回给模型和用户可见的文件摘要。
|
|
105
|
+
*/
|
|
106
|
+
function summarize_materialized_files(
|
|
107
|
+
parts: FileUIPart[],
|
|
108
|
+
project_root: string,
|
|
109
|
+
): PluginCallToolFileResult[] {
|
|
110
|
+
return parts.map((part, index) => ({
|
|
111
|
+
index,
|
|
112
|
+
media_type: part.mediaType,
|
|
113
|
+
filename: typeof part.filename === "string" ? part.filename : "",
|
|
114
|
+
url: String(part.url || ""),
|
|
115
|
+
path: resolve_resources_file_path(project_root, String(part.url || "")),
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
|
|
72
119
|
/**
|
|
73
120
|
* 生成给模型读取的短摘要。
|
|
74
121
|
*/
|
|
75
|
-
function summarize_action_data(data: JsonValue | undefined): JsonObject {
|
|
122
|
+
function summarize_action_data(data: JsonValue | undefined): JsonObject | undefined {
|
|
76
123
|
const message = to_json_object(data);
|
|
77
124
|
const parts: unknown[] = Array.isArray(message?.parts) ? message.parts : [];
|
|
78
|
-
const file_parts = parts.filter(is_file_part);
|
|
79
125
|
if (parts.length > 0) {
|
|
80
|
-
return
|
|
81
|
-
kind: "ui_message",
|
|
82
|
-
role: typeof message?.role === "string" ? message.role : "assistant",
|
|
83
|
-
part_count: parts.length,
|
|
84
|
-
file_count: file_parts.length,
|
|
85
|
-
files: file_parts.map((part, index) => ({
|
|
86
|
-
index,
|
|
87
|
-
mediaType: part.mediaType,
|
|
88
|
-
filename: typeof part.filename === "string" ? part.filename : "",
|
|
89
|
-
// 关键点(中文):不把 data URL 或长 URL 原样返回给模型,完整内容只进入 assistant file part。
|
|
90
|
-
has_url: Boolean(part.url),
|
|
91
|
-
})),
|
|
92
|
-
};
|
|
126
|
+
return undefined;
|
|
93
127
|
}
|
|
94
128
|
if (data === undefined) return {};
|
|
95
129
|
return {
|
|
@@ -135,29 +169,33 @@ export async function invokePluginCallTool(
|
|
|
135
169
|
action,
|
|
136
170
|
payload,
|
|
137
171
|
});
|
|
172
|
+
const project_root = resolve_project_root(getSessionRunContext()?.projectRoot);
|
|
138
173
|
const raw_file_parts = result.success
|
|
139
174
|
? extract_assistant_file_parts(result.data)
|
|
140
175
|
: [];
|
|
141
176
|
const file_parts =
|
|
142
177
|
raw_file_parts.length > 0
|
|
143
178
|
? await materializeAssistantFileParts({
|
|
144
|
-
projectRoot:
|
|
179
|
+
projectRoot: project_root,
|
|
145
180
|
parts: raw_file_parts,
|
|
146
181
|
})
|
|
147
182
|
: [];
|
|
183
|
+
const files = summarize_materialized_files(file_parts, project_root);
|
|
148
184
|
if (file_parts.length > 0) {
|
|
149
185
|
enqueueAssistantFileParts(file_parts);
|
|
150
186
|
}
|
|
187
|
+
const data = summarize_action_data(result.data);
|
|
151
188
|
return {
|
|
152
189
|
success: result.success,
|
|
153
190
|
plugin,
|
|
154
191
|
action,
|
|
155
192
|
assistant_file_count: file_parts.length,
|
|
193
|
+
...(files.length > 0 ? { files } : {}),
|
|
156
194
|
message:
|
|
157
195
|
String(result.message || result.error || "").trim() ||
|
|
158
196
|
(result.success ? "plugin action completed" : "plugin action failed"),
|
|
159
197
|
...(result.error ? { error: result.error } : {}),
|
|
160
|
-
data:
|
|
198
|
+
...(data === undefined ? {} : { data }),
|
|
161
199
|
};
|
|
162
200
|
} catch (error) {
|
|
163
201
|
return {
|
|
@@ -170,3 +208,31 @@ export async function invokePluginCallTool(
|
|
|
170
208
|
};
|
|
171
209
|
}
|
|
172
210
|
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 读取 plugin / action metadata。
|
|
214
|
+
*/
|
|
215
|
+
export async function invokePluginReadTool(
|
|
216
|
+
input: PluginReadInput,
|
|
217
|
+
): Promise<PluginReadToolResult> {
|
|
218
|
+
try {
|
|
219
|
+
const runtime = require_plugin_tool_runtime();
|
|
220
|
+
const data = runtime.read({
|
|
221
|
+
plugin: typeof input.plugin === "string" ? input.plugin : undefined,
|
|
222
|
+
action: typeof input.action === "string" ? input.action : undefined,
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
success: true,
|
|
226
|
+
message: "plugin metadata read",
|
|
227
|
+
data: data as unknown as JsonObject,
|
|
228
|
+
};
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return {
|
|
231
|
+
success: false,
|
|
232
|
+
message: String(error),
|
|
233
|
+
data: {
|
|
234
|
+
error: String(error),
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -8,9 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { tool } from "ai";
|
|
11
|
-
import type {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import type {
|
|
12
|
+
PluginCallInput,
|
|
13
|
+
PluginReadInput,
|
|
14
|
+
} from "@/executor/tools/plugin/types/PluginTool.js";
|
|
15
|
+
import {
|
|
16
|
+
invokePluginCallTool,
|
|
17
|
+
invokePluginReadTool,
|
|
18
|
+
setPluginToolRuntime,
|
|
19
|
+
} from "./PluginToolBridge.js";
|
|
20
|
+
import {
|
|
21
|
+
plugin_call_input_schema,
|
|
22
|
+
plugin_read_input_schema,
|
|
23
|
+
} from "./PluginToolSchemas.js";
|
|
14
24
|
|
|
15
25
|
export { setPluginToolRuntime } from "./PluginToolBridge.js";
|
|
16
26
|
|
|
@@ -19,14 +29,25 @@ export { setPluginToolRuntime } from "./PluginToolBridge.js";
|
|
|
19
29
|
*/
|
|
20
30
|
export const plugin_call = tool({
|
|
21
31
|
description:
|
|
22
|
-
"Call a registered agent plugin action. Use
|
|
32
|
+
"Call a registered agent plugin action. Use plugin_read first when you need the action list, input schema, or examples. Generated files may be attached to the final assistant message automatically.",
|
|
23
33
|
inputSchema: plugin_call_input_schema,
|
|
24
34
|
execute: async (input) => await invokePluginCallTool(input as PluginCallInput),
|
|
25
35
|
});
|
|
26
36
|
|
|
37
|
+
/**
|
|
38
|
+
* `plugin_read`:读取已注册 plugin / action metadata。
|
|
39
|
+
*/
|
|
40
|
+
export const plugin_read = tool({
|
|
41
|
+
description:
|
|
42
|
+
"Read registered agent plugin metadata, including action names, descriptions, input schemas, and examples. Use this before plugin_call when the payload shape is unclear.",
|
|
43
|
+
inputSchema: plugin_read_input_schema,
|
|
44
|
+
execute: async (input) => await invokePluginReadTool(input as PluginReadInput),
|
|
45
|
+
});
|
|
46
|
+
|
|
27
47
|
/**
|
|
28
48
|
* Plugin 工具导出集合。
|
|
29
49
|
*/
|
|
30
50
|
export const plugin_tools = {
|
|
31
51
|
plugin_call,
|
|
52
|
+
plugin_read,
|
|
32
53
|
};
|
|
@@ -10,7 +10,7 @@ import { z } from "zod";
|
|
|
10
10
|
|
|
11
11
|
export const plugin_call_input_schema = z.object({
|
|
12
12
|
plugin: z.string().describe("Registered plugin name to call, for example image."),
|
|
13
|
-
action: z.string().describe("Plugin action name to execute, for example
|
|
13
|
+
action: z.string().describe("Plugin action name to execute, for example image_create."),
|
|
14
14
|
payload: z
|
|
15
15
|
.object({})
|
|
16
16
|
.passthrough()
|
|
@@ -18,3 +18,14 @@ export const plugin_call_input_schema = z.object({
|
|
|
18
18
|
.default({})
|
|
19
19
|
.describe("JSON payload passed to the plugin action."),
|
|
20
20
|
});
|
|
21
|
+
|
|
22
|
+
export const plugin_read_input_schema = z.object({
|
|
23
|
+
plugin: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Registered plugin name to inspect. Omit to list plugins."),
|
|
27
|
+
action: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Plugin action name to inspect. Requires plugin."),
|
|
31
|
+
});
|
|
@@ -20,6 +20,32 @@ export interface PluginCallInput {
|
|
|
20
20
|
payload?: JsonObject;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* plugin_read 输入。
|
|
25
|
+
*/
|
|
26
|
+
export interface PluginReadInput {
|
|
27
|
+
/** 要读取的 plugin 名称;不传则列出 plugin 概览。 */
|
|
28
|
+
plugin?: string;
|
|
29
|
+
/** 要读取的 action 名称;仅在 plugin 存在时生效。 */
|
|
30
|
+
action?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* plugin_call 产生的 assistant 文件摘要。
|
|
35
|
+
*/
|
|
36
|
+
export interface PluginCallToolFileResult {
|
|
37
|
+
/** 文件在本轮 assistant 文件列表中的顺序。 */
|
|
38
|
+
index: number;
|
|
39
|
+
/** 文件 MIME 类型,例如 `image/png`。 */
|
|
40
|
+
media_type: string;
|
|
41
|
+
/** 原始文件名;若上游未提供则为空字符串。 */
|
|
42
|
+
filename: string;
|
|
43
|
+
/** 持久化到历史消息中的资源 URL,通常为 `resources://.downcity/resources/...`。 */
|
|
44
|
+
url: string;
|
|
45
|
+
/** 当前机器可直接打开的绝对文件路径。 */
|
|
46
|
+
path: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
23
49
|
/**
|
|
24
50
|
* plugin_call 返回给模型的摘要结果。
|
|
25
51
|
*/
|
|
@@ -32,6 +58,8 @@ export interface PluginCallToolResult {
|
|
|
32
58
|
action: string;
|
|
33
59
|
/** 本次 action 产生并写入 assistant 消息的 file part 数量。 */
|
|
34
60
|
assistant_file_count: number;
|
|
61
|
+
/** 本次 action 产生的文件摘要,包含可直接打开的绝对路径。 */
|
|
62
|
+
files?: PluginCallToolFileResult[];
|
|
35
63
|
/** 人类可读消息。 */
|
|
36
64
|
message: string;
|
|
37
65
|
/** 错误信息。 */
|
|
@@ -39,3 +67,15 @@ export interface PluginCallToolResult {
|
|
|
39
67
|
/** 返回给模型读取的短摘要数据。 */
|
|
40
68
|
data?: JsonObject;
|
|
41
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* plugin_read 返回给模型的 metadata。
|
|
73
|
+
*/
|
|
74
|
+
export interface PluginReadToolResult {
|
|
75
|
+
/** 调用是否成功。 */
|
|
76
|
+
success: boolean;
|
|
77
|
+
/** 人类可读消息。 */
|
|
78
|
+
message: string;
|
|
79
|
+
/** 读取到的 metadata。 */
|
|
80
|
+
data: JsonObject;
|
|
81
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -72,6 +72,14 @@ export type {
|
|
|
72
72
|
|
|
73
73
|
// Plugin 作者 API
|
|
74
74
|
export { BasePlugin } from "./plugin/core/BasePlugin.js";
|
|
75
|
+
export {
|
|
76
|
+
createAction,
|
|
77
|
+
createPlugin,
|
|
78
|
+
} from "./plugin/core/PluginActionFactory.js";
|
|
79
|
+
export type {
|
|
80
|
+
CreatePluginActionOptions,
|
|
81
|
+
CreatePluginOptions,
|
|
82
|
+
} from "./plugin/core/PluginActionFactory.js";
|
|
75
83
|
|
|
76
84
|
// Session 与即时执行集成
|
|
77
85
|
export { Executor } from "./executor/Executor.js";
|
|
@@ -229,6 +237,9 @@ export type {
|
|
|
229
237
|
PluginActionApi,
|
|
230
238
|
PluginActionCommand,
|
|
231
239
|
PluginActionCommandInput,
|
|
240
|
+
PluginActionExample,
|
|
241
|
+
PluginActionInputSchema,
|
|
242
|
+
PluginActionMetadata,
|
|
232
243
|
PluginActionResult,
|
|
233
244
|
PluginActions,
|
|
234
245
|
PluginAvailability,
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginActionFactory:创建带 metadata 和 schema 的 plugin/action。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - `createAction` 让 action 的运行时 schema 与 TypeScript 输入类型保持一致。
|
|
6
|
+
* - `createPlugin` 只做轻量对象装配,不强制继承 BasePlugin。
|
|
7
|
+
* - 旧的 class extends BasePlugin 写法仍可继续使用。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { z } from "zod";
|
|
11
|
+
import type { AgentContext } from "@/types/runtime/agent/AgentContext.js";
|
|
12
|
+
import type { JsonValue } from "@/types/common/Json.js";
|
|
13
|
+
import type {
|
|
14
|
+
PluginAction,
|
|
15
|
+
PluginActionApi,
|
|
16
|
+
PluginActionCommand,
|
|
17
|
+
PluginActionExample,
|
|
18
|
+
PluginActionInputSchema,
|
|
19
|
+
PluginActionResult,
|
|
20
|
+
PluginActions,
|
|
21
|
+
} from "@/types/plugin/PluginAction.js";
|
|
22
|
+
import type { Plugin } from "@/types/plugin/PluginDefinition.js";
|
|
23
|
+
import type {
|
|
24
|
+
PluginAvailability,
|
|
25
|
+
PluginConfigDefinition,
|
|
26
|
+
PluginHooks,
|
|
27
|
+
PluginResolves,
|
|
28
|
+
} from "@/types/plugin/PluginRuntime.js";
|
|
29
|
+
import type {
|
|
30
|
+
PluginCommandContext,
|
|
31
|
+
PluginLifecycle,
|
|
32
|
+
} from "@/types/plugin/PluginCommand.js";
|
|
33
|
+
import type {
|
|
34
|
+
PluginSetupDefinition,
|
|
35
|
+
PluginUsageDefinition,
|
|
36
|
+
} from "@/types/plugin/PluginSetup.js";
|
|
37
|
+
import type { PluginHttpDefinition } from "@/types/plugin/PluginHttp.js";
|
|
38
|
+
import type { StructuredConfig } from "@/types/runtime/agent/AgentContext.js";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 从 Zod schema 推导 JSON 输入类型。
|
|
42
|
+
*/
|
|
43
|
+
type InferZodJson<TSchema extends z.ZodTypeAny> =
|
|
44
|
+
z.infer<TSchema> extends JsonValue ? z.infer<TSchema> : JsonValue;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* createAction 参数。
|
|
48
|
+
*/
|
|
49
|
+
export interface CreatePluginActionOptions<
|
|
50
|
+
P extends JsonValue,
|
|
51
|
+
R extends JsonValue,
|
|
52
|
+
> {
|
|
53
|
+
/** Action 用途说明。 */
|
|
54
|
+
description?: string;
|
|
55
|
+
/** Zod 输入 schema。 */
|
|
56
|
+
input_schema?: z.ZodTypeAny | PluginActionInputSchema<P>;
|
|
57
|
+
/** Action 调用示例。 */
|
|
58
|
+
examples?: PluginActionExample<P>[];
|
|
59
|
+
/** disabled 状态下是否仍允许执行。 */
|
|
60
|
+
allowWhenDisabled?: boolean;
|
|
61
|
+
/** CLI 定义。 */
|
|
62
|
+
command?: PluginActionCommand<P>;
|
|
63
|
+
/** HTTP 定义。 */
|
|
64
|
+
api?: PluginActionApi<P>;
|
|
65
|
+
/** Action 执行器。 */
|
|
66
|
+
execute: (params: {
|
|
67
|
+
/** 当前执行上下文。 */
|
|
68
|
+
context: AgentContext;
|
|
69
|
+
/** 已通过 schema 校验后的输入。 */
|
|
70
|
+
input: P;
|
|
71
|
+
/** 兼容旧命名的 payload。 */
|
|
72
|
+
payload: P;
|
|
73
|
+
/** 当前插件名称。 */
|
|
74
|
+
pluginName: string;
|
|
75
|
+
/** 当前 Action 名称。 */
|
|
76
|
+
actionName: string;
|
|
77
|
+
}) => Promise<PluginActionResult<R>> | PluginActionResult<R>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* createPlugin 参数。
|
|
82
|
+
*/
|
|
83
|
+
export interface CreatePluginOptions<TActions extends PluginActions> {
|
|
84
|
+
/** Plugin 稳定名称。 */
|
|
85
|
+
name: string;
|
|
86
|
+
/** Plugin 展示标题。 */
|
|
87
|
+
title?: string;
|
|
88
|
+
/** Plugin 用途说明。 */
|
|
89
|
+
description?: string;
|
|
90
|
+
/** Plugin 显式 action 集合。 */
|
|
91
|
+
actions?: TActions;
|
|
92
|
+
/** Plugin 配置定义。 */
|
|
93
|
+
config?: PluginConfigDefinition<StructuredConfig>;
|
|
94
|
+
/** Plugin setup 定义。 */
|
|
95
|
+
setup?: PluginSetupDefinition;
|
|
96
|
+
/** Plugin usage 定义。 */
|
|
97
|
+
usage?: PluginUsageDefinition;
|
|
98
|
+
/** Plugin hook 集合。 */
|
|
99
|
+
hooks?: PluginHooks;
|
|
100
|
+
/** Plugin resolve 集合。 */
|
|
101
|
+
resolves?: PluginResolves;
|
|
102
|
+
/** Plugin system 文本构建器。 */
|
|
103
|
+
system?: (context: AgentContext) => string | Promise<string>;
|
|
104
|
+
/** 旧生命周期兼容定义。 */
|
|
105
|
+
lifecycle?: PluginLifecycle;
|
|
106
|
+
/** Plugin 可用性检查。 */
|
|
107
|
+
availability?: (
|
|
108
|
+
context: PluginCommandContext | AgentContext,
|
|
109
|
+
) => Promise<PluginAvailability> | PluginAvailability;
|
|
110
|
+
/** Plugin HTTP 注入定义。 */
|
|
111
|
+
http?: PluginHttpDefinition;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 归一化输入 schema。
|
|
116
|
+
*/
|
|
117
|
+
function normalize_input_schema<P extends JsonValue>(
|
|
118
|
+
input_schema: z.ZodTypeAny | PluginActionInputSchema<P> | undefined,
|
|
119
|
+
): PluginActionInputSchema<P> | undefined {
|
|
120
|
+
if (!input_schema) return undefined;
|
|
121
|
+
if (typeof (input_schema as z.ZodTypeAny).safeParse === "function") {
|
|
122
|
+
return { zod: input_schema as z.ZodTypeAny };
|
|
123
|
+
}
|
|
124
|
+
return input_schema as PluginActionInputSchema<P>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 创建带 metadata 的 action。
|
|
129
|
+
*/
|
|
130
|
+
export function createAction<
|
|
131
|
+
TSchema extends z.ZodTypeAny,
|
|
132
|
+
R extends JsonValue = JsonValue,
|
|
133
|
+
>(
|
|
134
|
+
options: CreatePluginActionOptions<InferZodJson<TSchema>, R> & {
|
|
135
|
+
/** Zod 输入 schema。 */
|
|
136
|
+
input_schema?: TSchema | PluginActionInputSchema<InferZodJson<TSchema>>;
|
|
137
|
+
},
|
|
138
|
+
): PluginAction<InferZodJson<TSchema>, R>;
|
|
139
|
+
export function createAction<R extends JsonValue = JsonValue>(
|
|
140
|
+
options: CreatePluginActionOptions<JsonValue, R>,
|
|
141
|
+
): PluginAction<JsonValue, R>;
|
|
142
|
+
export function createAction(
|
|
143
|
+
options: CreatePluginActionOptions<JsonValue, JsonValue>,
|
|
144
|
+
): PluginAction<JsonValue, JsonValue> {
|
|
145
|
+
return {
|
|
146
|
+
...(options.description ? { description: options.description } : {}),
|
|
147
|
+
...(options.input_schema
|
|
148
|
+
? { input_schema: normalize_input_schema(options.input_schema) }
|
|
149
|
+
: {}),
|
|
150
|
+
...(options.examples ? { examples: options.examples } : {}),
|
|
151
|
+
...(typeof options.allowWhenDisabled === "boolean"
|
|
152
|
+
? { allowWhenDisabled: options.allowWhenDisabled }
|
|
153
|
+
: {}),
|
|
154
|
+
...(options.command ? { command: options.command } : {}),
|
|
155
|
+
...(options.api ? { api: options.api } : {}),
|
|
156
|
+
execute: options.execute,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 创建 plugin 对象。
|
|
162
|
+
*/
|
|
163
|
+
export function createPlugin<TActions extends PluginActions>(
|
|
164
|
+
options: CreatePluginOptions<TActions>,
|
|
165
|
+
): Plugin & { actions: TActions } {
|
|
166
|
+
const name = String(options.name || "").trim();
|
|
167
|
+
if (!name) {
|
|
168
|
+
throw new Error("createPlugin requires a non-empty name");
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
name,
|
|
172
|
+
title: String(options.title || name).trim(),
|
|
173
|
+
description: String(options.description || "").trim(),
|
|
174
|
+
actions: options.actions || ({} as TActions),
|
|
175
|
+
...(options.config ? { config: options.config } : {}),
|
|
176
|
+
...(options.setup ? { setup: options.setup } : {}),
|
|
177
|
+
...(options.usage ? { usage: options.usage } : {}),
|
|
178
|
+
...(options.hooks ? { hooks: options.hooks } : {}),
|
|
179
|
+
...(options.resolves ? { resolves: options.resolves } : {}),
|
|
180
|
+
...(options.system ? { system: options.system } : {}),
|
|
181
|
+
...(options.lifecycle ? { lifecycle: options.lifecycle } : {}),
|
|
182
|
+
...(options.availability ? { availability: options.availability } : {}),
|
|
183
|
+
...(options.http ? { http: options.http } : {}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -58,9 +58,22 @@ export async function invokePluginAction(params: {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
try {
|
|
61
|
+
const payload = (params.payload ?? {}) as JsonValue;
|
|
62
|
+
const schema = action.input_schema?.zod;
|
|
63
|
+
const parsed_payload = schema ? schema.safeParse(payload) : null;
|
|
64
|
+
if (parsed_payload && !parsed_payload.success) {
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
error: `Invalid payload for ${params.plugin.name}.${params.actionName}: ${parsed_payload.error.message}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const input_payload = parsed_payload?.success
|
|
71
|
+
? parsed_payload.data as JsonValue
|
|
72
|
+
: payload;
|
|
61
73
|
return await action.execute({
|
|
62
74
|
context: params.context,
|
|
63
|
-
payload:
|
|
75
|
+
payload: input_payload,
|
|
76
|
+
input: input_payload,
|
|
64
77
|
pluginName: params.plugin.name,
|
|
65
78
|
actionName: params.actionName,
|
|
66
79
|
});
|
|
@@ -135,9 +135,23 @@ export async function runLocalPluginAction(params: {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
try {
|
|
138
|
+
const payload = (params.payload ?? {}) as JsonValue;
|
|
139
|
+
const schema = action.input_schema?.zod;
|
|
140
|
+
const parsed_payload = schema ? schema.safeParse(payload) : null;
|
|
141
|
+
if (parsed_payload && !parsed_payload.success) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: `Invalid payload for ${plugin.name}.${actionName}: ${parsed_payload.error.message}`,
|
|
145
|
+
message: `Invalid payload for ${plugin.name}.${actionName}`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const input_payload = parsed_payload?.success
|
|
149
|
+
? parsed_payload.data as JsonValue
|
|
150
|
+
: payload;
|
|
138
151
|
return await action.execute({
|
|
139
152
|
context: context as unknown as AgentContext,
|
|
140
|
-
payload:
|
|
153
|
+
payload: input_payload,
|
|
154
|
+
input: input_payload,
|
|
141
155
|
pluginName: plugin.name,
|
|
142
156
|
actionName,
|
|
143
157
|
});
|
|
@@ -14,6 +14,8 @@ import type {
|
|
|
14
14
|
PluginActionResult,
|
|
15
15
|
AgentPlugins,
|
|
16
16
|
PluginAvailability,
|
|
17
|
+
PluginActionReadView,
|
|
18
|
+
PluginReadView,
|
|
17
19
|
PluginView,
|
|
18
20
|
} from "@/plugin/types/Plugin.js";
|
|
19
21
|
import type { AgentContext } from "@/types/runtime/agent/AgentContext.js";
|
|
@@ -128,6 +130,60 @@ export class PluginRegistry implements AgentPlugins {
|
|
|
128
130
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
129
131
|
}
|
|
130
132
|
|
|
133
|
+
/**
|
|
134
|
+
* 读取 action metadata。
|
|
135
|
+
*/
|
|
136
|
+
private readAction(
|
|
137
|
+
actionName: string,
|
|
138
|
+
action: NonNullable<Plugin["actions"]>[string],
|
|
139
|
+
): PluginActionReadView {
|
|
140
|
+
return {
|
|
141
|
+
name: actionName,
|
|
142
|
+
description: String(action.description || "").trim(),
|
|
143
|
+
has_input_schema: Boolean(action.input_schema),
|
|
144
|
+
...(action.input_schema?.json_schema
|
|
145
|
+
? { input_schema: action.input_schema.json_schema }
|
|
146
|
+
: {}),
|
|
147
|
+
...(action.examples ? { examples: action.examples } : {}),
|
|
148
|
+
allow_when_disabled: action.allowWhenDisabled === true,
|
|
149
|
+
has_command: Boolean(action.command),
|
|
150
|
+
has_api: Boolean(action.api),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 读取 plugin / action metadata。
|
|
156
|
+
*/
|
|
157
|
+
read(params: {
|
|
158
|
+
plugin?: string;
|
|
159
|
+
action?: string;
|
|
160
|
+
}): PluginReadView | { plugins: PluginView[] } {
|
|
161
|
+
const pluginName = String(params.plugin || "").trim();
|
|
162
|
+
if (!pluginName) {
|
|
163
|
+
return { plugins: this.list() };
|
|
164
|
+
}
|
|
165
|
+
const plugin = this.get(pluginName);
|
|
166
|
+
if (!plugin) {
|
|
167
|
+
return {
|
|
168
|
+
name: pluginName,
|
|
169
|
+
title: pluginName,
|
|
170
|
+
description: "",
|
|
171
|
+
actions: [],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const actionName = String(params.action || "").trim();
|
|
175
|
+
const actions = Object.entries(plugin.actions || {})
|
|
176
|
+
.filter(([name]) => !actionName || name === actionName)
|
|
177
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
178
|
+
.map(([name, action]) => this.readAction(name, action));
|
|
179
|
+
return {
|
|
180
|
+
name: plugin.name,
|
|
181
|
+
title: String(plugin.title || plugin.name || "").trim(),
|
|
182
|
+
description: String(plugin.description || "").trim(),
|
|
183
|
+
actions,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
131
187
|
/**
|
|
132
188
|
* 检查 plugin 可用性。
|
|
133
189
|
*/
|
|
@@ -162,6 +218,28 @@ export class PluginRegistry implements AgentPlugins {
|
|
|
162
218
|
};
|
|
163
219
|
}
|
|
164
220
|
|
|
221
|
+
/**
|
|
222
|
+
* 按 action schema 校验 payload。
|
|
223
|
+
*/
|
|
224
|
+
private parseActionPayload(params: {
|
|
225
|
+
pluginName: string;
|
|
226
|
+
actionName: string;
|
|
227
|
+
payload: JsonValue;
|
|
228
|
+
action: NonNullable<Plugin["actions"]>[string];
|
|
229
|
+
}): PluginActionResult<JsonValue> | { input: JsonValue } {
|
|
230
|
+
const schema = params.action.input_schema?.zod;
|
|
231
|
+
if (!schema) return { input: params.payload };
|
|
232
|
+
const parsed = schema.safeParse(params.payload);
|
|
233
|
+
if (parsed.success) {
|
|
234
|
+
return { input: parsed.data as JsonValue };
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
error: `Invalid payload for ${params.pluginName}.${params.actionName}: ${parsed.error.message}`,
|
|
239
|
+
message: `Invalid payload for ${params.pluginName}.${params.actionName}`,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
165
243
|
/**
|
|
166
244
|
* 运行 plugin action。
|
|
167
245
|
*/
|
|
@@ -208,9 +286,19 @@ export class PluginRegistry implements AgentPlugins {
|
|
|
208
286
|
}
|
|
209
287
|
|
|
210
288
|
try {
|
|
289
|
+
const parsed_payload = this.parseActionPayload({
|
|
290
|
+
pluginName: plugin.name,
|
|
291
|
+
actionName,
|
|
292
|
+
payload: (params.payload ?? {}) as JsonValue,
|
|
293
|
+
action,
|
|
294
|
+
});
|
|
295
|
+
if (!("input" in parsed_payload)) {
|
|
296
|
+
return parsed_payload;
|
|
297
|
+
}
|
|
211
298
|
return await action.execute({
|
|
212
299
|
context,
|
|
213
|
-
payload:
|
|
300
|
+
payload: parsed_payload.input,
|
|
301
|
+
input: parsed_payload.input,
|
|
214
302
|
pluginName: plugin.name,
|
|
215
303
|
actionName,
|
|
216
304
|
});
|
|
@@ -24,9 +24,12 @@ export type {
|
|
|
24
24
|
PluginActionApi,
|
|
25
25
|
PluginActionCommand,
|
|
26
26
|
PluginActionCommandInput,
|
|
27
|
+
PluginActionExample,
|
|
27
28
|
PluginActionInvokeParams,
|
|
28
29
|
PluginActionInvokePort,
|
|
29
30
|
PluginActionInvokeResult,
|
|
31
|
+
PluginActionInputSchema,
|
|
32
|
+
PluginActionMetadata,
|
|
30
33
|
PluginActionResult,
|
|
31
34
|
PluginActions,
|
|
32
35
|
} from "@/types/plugin/PluginAction.js";
|
|
@@ -38,8 +41,10 @@ export type {
|
|
|
38
41
|
PluginHooks,
|
|
39
42
|
PluginPipelineHook,
|
|
40
43
|
AgentPlugins,
|
|
44
|
+
PluginActionReadView,
|
|
41
45
|
PluginResolveHook,
|
|
42
46
|
PluginResolves,
|
|
47
|
+
PluginReadView,
|
|
43
48
|
PluginView,
|
|
44
49
|
} from "@/types/plugin/PluginRuntime.js";
|
|
45
50
|
export type {
|