@downcity/plugins 1.0.51 → 1.0.56
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/BuiltinPlugins.d.ts +2 -3
- package/bin/BuiltinPlugins.d.ts.map +1 -1
- package/bin/BuiltinPlugins.js +2 -2
- package/bin/BuiltinPlugins.js.map +1 -1
- package/bin/asr/Plugin.d.ts +1 -2
- package/bin/asr/Plugin.d.ts.map +1 -1
- package/bin/asr/Plugin.js +2 -2
- package/bin/asr/Plugin.js.map +1 -1
- package/bin/auth/Plugin.d.ts +1 -2
- package/bin/auth/Plugin.d.ts.map +1 -1
- package/bin/auth/Plugin.js +2 -2
- package/bin/auth/Plugin.js.map +1 -1
- package/bin/chat/ChatPlugin.d.ts +1 -2
- package/bin/chat/ChatPlugin.d.ts.map +1 -1
- package/bin/chat/ChatPlugin.js +3 -12
- package/bin/chat/ChatPlugin.js.map +1 -1
- package/bin/chat/PROMPT.direct.d.ts +1 -1
- package/bin/chat/PROMPT.direct.d.ts.map +1 -1
- package/bin/chat/PROMPT.direct.js +1 -1
- package/bin/chat/PROMPT.direct.js.map +1 -1
- package/bin/chat/channels/qq/PROMPT.direct.d.ts +1 -1
- package/bin/chat/channels/qq/PROMPT.direct.d.ts.map +1 -1
- package/bin/chat/channels/qq/PROMPT.direct.js +1 -1
- package/bin/chat/channels/qq/PROMPT.direct.js.map +1 -1
- package/bin/chat/channels/telegram/Bot.d.ts +6 -0
- package/bin/chat/channels/telegram/Bot.d.ts.map +1 -1
- package/bin/chat/channels/telegram/Bot.js +24 -0
- package/bin/chat/channels/telegram/Bot.js.map +1 -1
- package/bin/chat/channels/telegram/TelegramPlatformClient.d.ts +14 -0
- package/bin/chat/channels/telegram/TelegramPlatformClient.d.ts.map +1 -1
- package/bin/chat/channels/telegram/TelegramPlatformClient.js +39 -5
- package/bin/chat/channels/telegram/TelegramPlatformClient.js.map +1 -1
- package/bin/contact/ContactPlugin.d.ts +1 -2
- package/bin/contact/ContactPlugin.d.ts.map +1 -1
- package/bin/contact/ContactPlugin.js +2 -2
- package/bin/contact/ContactPlugin.js.map +1 -1
- package/bin/contact/PROMPT.d.ts +1 -1
- package/bin/contact/PROMPT.d.ts.map +1 -1
- package/bin/contact/PROMPT.js +1 -1
- package/bin/contact/PROMPT.js.map +1 -1
- package/bin/image/ImagePlugin.d.ts +61 -0
- package/bin/image/ImagePlugin.d.ts.map +1 -0
- package/bin/image/ImagePlugin.js +189 -0
- package/bin/image/ImagePlugin.js.map +1 -0
- package/bin/image/types/ImagePlugin.d.ts +136 -0
- package/bin/image/types/ImagePlugin.d.ts.map +1 -0
- package/bin/image/types/ImagePlugin.js +10 -0
- package/bin/image/types/ImagePlugin.js.map +1 -0
- package/bin/index.d.ts +2 -0
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +1 -0
- package/bin/index.js.map +1 -1
- package/bin/shell/ShellPlugin.d.ts +1 -2
- package/bin/shell/ShellPlugin.d.ts.map +1 -1
- package/bin/shell/ShellPlugin.js +2 -2
- package/bin/shell/ShellPlugin.js.map +1 -1
- package/bin/skill/Action.d.ts +8 -3
- package/bin/skill/Action.d.ts.map +1 -1
- package/bin/skill/Action.js +9 -11
- package/bin/skill/Action.js.map +1 -1
- package/bin/skill/Command.d.ts +1 -1
- package/bin/skill/Command.d.ts.map +1 -1
- package/bin/skill/Command.js +3 -10
- package/bin/skill/Command.js.map +1 -1
- package/bin/skill/Config.d.ts +9 -9
- package/bin/skill/Config.d.ts.map +1 -1
- package/bin/skill/Config.js +36 -24
- package/bin/skill/Config.js.map +1 -1
- package/bin/skill/PROMPT.d.ts +1 -1
- package/bin/skill/PROMPT.d.ts.map +1 -1
- package/bin/skill/PROMPT.js +1 -1
- package/bin/skill/PROMPT.js.map +1 -1
- package/bin/skill/Plugin.d.ts +2 -2
- package/bin/skill/Plugin.d.ts.map +1 -1
- package/bin/skill/Plugin.js +20 -37
- package/bin/skill/Plugin.js.map +1 -1
- package/bin/skill/runtime/Discovery.d.ts +3 -3
- package/bin/skill/runtime/Discovery.d.ts.map +1 -1
- package/bin/skill/runtime/Discovery.js +37 -14
- package/bin/skill/runtime/Discovery.js.map +1 -1
- package/bin/skill/runtime/Paths.d.ts +5 -5
- package/bin/skill/runtime/Paths.d.ts.map +1 -1
- package/bin/skill/runtime/Paths.js +23 -33
- package/bin/skill/runtime/Paths.js.map +1 -1
- package/bin/skill/runtime/Prompt.d.ts +2 -2
- package/bin/skill/runtime/Prompt.d.ts.map +1 -1
- package/bin/skill/runtime/Prompt.js +3 -8
- package/bin/skill/runtime/Prompt.js.map +1 -1
- package/bin/skill/runtime/SystemProvider.d.ts +2 -2
- package/bin/skill/runtime/SystemProvider.d.ts.map +1 -1
- package/bin/skill/runtime/SystemProvider.js +2 -2
- package/bin/skill/runtime/SystemProvider.js.map +1 -1
- package/bin/skill/types/SkillPlugin.d.ts +34 -12
- package/bin/skill/types/SkillPlugin.d.ts.map +1 -1
- package/bin/skill/types/SkillPlugin.js +2 -1
- package/bin/skill/types/SkillPlugin.js.map +1 -1
- package/bin/skill/types/SkillRoot.d.ts +1 -1
- package/bin/task/Action.js +2 -2
- package/bin/task/Action.js.map +1 -1
- package/bin/task/PROMPT.d.ts +1 -1
- package/bin/task/PROMPT.d.ts.map +1 -1
- package/bin/task/PROMPT.js +1 -1
- package/bin/task/PROMPT.js.map +1 -1
- package/bin/task/TaskPlugin.d.ts +1 -2
- package/bin/task/TaskPlugin.d.ts.map +1 -1
- package/bin/task/TaskPlugin.js +2 -2
- package/bin/task/TaskPlugin.js.map +1 -1
- package/bin/tts/Plugin.d.ts +1 -2
- package/bin/tts/Plugin.d.ts.map +1 -1
- package/bin/tts/Plugin.js +6 -6
- package/bin/tts/Plugin.js.map +1 -1
- package/bin/web/PROMPT.agent-browser.d.ts +1 -1
- package/bin/web/PROMPT.agent-browser.d.ts.map +1 -1
- package/bin/web/PROMPT.agent-browser.js +1 -1
- package/bin/web/PROMPT.agent-browser.js.map +1 -1
- package/bin/web/Plugin.d.ts +1 -2
- package/bin/web/Plugin.d.ts.map +1 -1
- package/bin/web/Plugin.js +10 -3
- package/bin/web/Plugin.js.map +1 -1
- package/bin/workboard/Plugin.d.ts +1 -2
- package/bin/workboard/Plugin.d.ts.map +1 -1
- package/bin/workboard/Plugin.js +2 -2
- package/bin/workboard/Plugin.js.map +1 -1
- package/package.json +2 -2
- package/src/BuiltinPlugins.ts +3 -6
- package/src/asr/Plugin.ts +2 -3
- package/src/auth/Plugin.ts +2 -3
- package/src/chat/ChatPlugin.ts +3 -14
- package/src/chat/PROMPT.direct.ts +1 -1
- package/src/chat/PROMPT.direct.ts.txt +5 -7
- package/src/chat/channels/qq/PROMPT.direct.ts +1 -1
- package/src/chat/channels/qq/PROMPT.direct.ts.txt +1 -1
- package/src/chat/channels/telegram/Bot.ts +22 -0
- package/src/chat/channels/telegram/TelegramPlatformClient.ts +42 -6
- package/src/contact/ContactPlugin.ts +2 -3
- package/src/contact/PROMPT.ts +1 -1
- package/src/contact/PROMPT.ts.txt +12 -12
- package/src/image/ImagePlugin.ts +250 -0
- package/src/image/types/ImagePlugin.ts +157 -0
- package/src/index.ts +13 -0
- package/src/shell/ShellPlugin.ts +2 -3
- package/src/skill/Action.ts +20 -9
- package/src/skill/Command.ts +3 -13
- package/src/skill/Config.ts +41 -34
- package/src/skill/PROMPT.ts +1 -1
- package/src/skill/PROMPT.ts.txt +14 -2
- package/src/skill/Plugin.ts +205 -214
- package/src/skill/runtime/Discovery.ts +43 -17
- package/src/skill/runtime/Paths.ts +24 -35
- package/src/skill/runtime/Prompt.ts +4 -10
- package/src/skill/runtime/SystemProvider.ts +4 -4
- package/src/skill/types/SkillPlugin.ts +39 -12
- package/src/skill/types/SkillRoot.ts +1 -1
- package/src/task/Action.ts +2 -2
- package/src/task/PROMPT.ts +1 -1
- package/src/task/PROMPT.ts.txt +10 -10
- package/src/task/TaskPlugin.ts +2 -3
- package/src/tts/Plugin.ts +6 -7
- package/src/web/PROMPT.agent-browser.ts +1 -1
- package/src/web/PROMPT.agent-browser.ts.txt +1 -1
- package/src/web/Plugin.ts +3 -4
- package/src/workboard/Plugin.ts +2 -3
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ImagePlugin:图片生成插件。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - 对 Agent 只暴露同步体验的 `generate` action。
|
|
6
|
+
* - City / provider 的图片能力通过 image_create / image_result 任务函数注入。
|
|
7
|
+
* - action 返回 AI SDK UIMessage,后续由 plugin tool bridge 抽取 file parts 写回 assistant 消息。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { BasePlugin } from "@downcity/agent/internal/plugin/core/BasePlugin.js";
|
|
11
|
+
import type { AgentContext } from "@downcity/agent/internal/types/runtime/agent/AgentContext.js";
|
|
12
|
+
import type {
|
|
13
|
+
JsonObject,
|
|
14
|
+
JsonValue,
|
|
15
|
+
} from "@downcity/agent/internal/types/common/Json.js";
|
|
16
|
+
import type {
|
|
17
|
+
ImagePluginInput,
|
|
18
|
+
ImagePluginJobCreateResult,
|
|
19
|
+
ImagePluginJobResult,
|
|
20
|
+
ImagePluginOptions,
|
|
21
|
+
ImagePluginResult,
|
|
22
|
+
} from "@/image/types/ImagePlugin.js";
|
|
23
|
+
|
|
24
|
+
const DEFAULT_IMAGE_PLUGIN_NAME = "image";
|
|
25
|
+
const DEFAULT_IMAGE_PLUGIN_TITLE = "Image";
|
|
26
|
+
const DEFAULT_IMAGE_PLUGIN_DESCRIPTION =
|
|
27
|
+
"Generate images and return them as assistant file parts.";
|
|
28
|
+
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
29
|
+
const DEFAULT_MIN_POLL_INTERVAL_MS = 100;
|
|
30
|
+
const DEFAULT_MAX_POLL_INTERVAL_MS = 10_000;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 判断值是否为普通对象。
|
|
34
|
+
*/
|
|
35
|
+
function to_record(value: unknown): Record<string, unknown> | null {
|
|
36
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
37
|
+
return value as Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 归一化模型传入的图片生成 payload。
|
|
42
|
+
*/
|
|
43
|
+
function normalize_image_payload(
|
|
44
|
+
payload: JsonValue | undefined,
|
|
45
|
+
): ImagePluginInput {
|
|
46
|
+
const record = to_record(payload ?? {});
|
|
47
|
+
if (!record) {
|
|
48
|
+
throw new TypeError("ImagePlugin.generate payload must be an object");
|
|
49
|
+
}
|
|
50
|
+
return { ...record } as ImagePluginInput;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 校验 image 函数返回的 UIMessage。
|
|
55
|
+
*/
|
|
56
|
+
function normalize_image_result(result: ImagePluginResult): ImagePluginResult {
|
|
57
|
+
const record = to_record(result);
|
|
58
|
+
if (!record || !Array.isArray(record.parts)) {
|
|
59
|
+
throw new TypeError("ImagePlugin image provider must return an AI SDK UIMessage");
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 等待指定毫秒数。
|
|
66
|
+
*/
|
|
67
|
+
function sleep(ms: number): Promise<void> {
|
|
68
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 限制轮询间隔,避免服务端异常值导致过快或过慢轮询。
|
|
73
|
+
*/
|
|
74
|
+
function clamp_poll_interval(
|
|
75
|
+
value: unknown,
|
|
76
|
+
min_ms: number,
|
|
77
|
+
max_ms: number,
|
|
78
|
+
): number {
|
|
79
|
+
const n = typeof value === "number" && Number.isFinite(value) ? value : min_ms;
|
|
80
|
+
return Math.max(min_ms, Math.min(max_ms, n));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 归一化正数配置。
|
|
85
|
+
*/
|
|
86
|
+
function normalize_positive_number(value: unknown, fallback: number): number {
|
|
87
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
88
|
+
? value
|
|
89
|
+
: fallback;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 校验任务创建结果。
|
|
94
|
+
*/
|
|
95
|
+
function validate_created_job(value: ImagePluginJobCreateResult): void {
|
|
96
|
+
if (
|
|
97
|
+
!value ||
|
|
98
|
+
typeof value !== "object" ||
|
|
99
|
+
typeof value.job_id !== "string" ||
|
|
100
|
+
!value.job_id.trim()
|
|
101
|
+
) {
|
|
102
|
+
throw new TypeError("ImagePlugin image_create must return a job_id");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 校验任务查询结果。
|
|
108
|
+
*/
|
|
109
|
+
function validate_job_result(value: ImagePluginJobResult): void {
|
|
110
|
+
const status = value?.status;
|
|
111
|
+
if (
|
|
112
|
+
status !== "queued" &&
|
|
113
|
+
status !== "running" &&
|
|
114
|
+
status !== "succeeded" &&
|
|
115
|
+
status !== "failed"
|
|
116
|
+
) {
|
|
117
|
+
throw new TypeError("ImagePlugin image_result must return a valid job status");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Agent 图片生成插件。
|
|
123
|
+
*/
|
|
124
|
+
export class ImagePlugin extends BasePlugin {
|
|
125
|
+
/**
|
|
126
|
+
* 当前 plugin 稳定名称。
|
|
127
|
+
*/
|
|
128
|
+
readonly name: string;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 插件标题。
|
|
132
|
+
*/
|
|
133
|
+
readonly title: string;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 插件说明。
|
|
137
|
+
*/
|
|
138
|
+
readonly description: string;
|
|
139
|
+
|
|
140
|
+
private readonly image_create: NonNullable<ImagePluginOptions["image_create"]>;
|
|
141
|
+
private readonly image_result: NonNullable<ImagePluginOptions["image_result"]>;
|
|
142
|
+
private readonly timeout_ms: number;
|
|
143
|
+
private readonly min_poll_interval_ms: number;
|
|
144
|
+
private readonly max_poll_interval_ms: number;
|
|
145
|
+
|
|
146
|
+
constructor(options: ImagePluginOptions) {
|
|
147
|
+
super();
|
|
148
|
+
const name = String(options.name || DEFAULT_IMAGE_PLUGIN_NAME).trim();
|
|
149
|
+
if (!name) {
|
|
150
|
+
throw new Error("ImagePlugin requires a non-empty name");
|
|
151
|
+
}
|
|
152
|
+
if (typeof options.image_create !== "function") {
|
|
153
|
+
throw new Error("ImagePlugin requires an image_create function");
|
|
154
|
+
}
|
|
155
|
+
if (typeof options.image_result !== "function") {
|
|
156
|
+
throw new Error("ImagePlugin requires an image_result function");
|
|
157
|
+
}
|
|
158
|
+
this.name = name;
|
|
159
|
+
this.title = String(options.title || DEFAULT_IMAGE_PLUGIN_TITLE).trim();
|
|
160
|
+
this.description = String(
|
|
161
|
+
options.description || DEFAULT_IMAGE_PLUGIN_DESCRIPTION,
|
|
162
|
+
).trim();
|
|
163
|
+
this.image_create = options.image_create;
|
|
164
|
+
this.image_result = options.image_result;
|
|
165
|
+
this.timeout_ms = normalize_positive_number(
|
|
166
|
+
options.timeout_ms,
|
|
167
|
+
DEFAULT_TIMEOUT_MS,
|
|
168
|
+
);
|
|
169
|
+
this.min_poll_interval_ms = normalize_positive_number(
|
|
170
|
+
options.min_poll_interval_ms,
|
|
171
|
+
DEFAULT_MIN_POLL_INTERVAL_MS,
|
|
172
|
+
);
|
|
173
|
+
this.max_poll_interval_ms = normalize_positive_number(
|
|
174
|
+
options.max_poll_interval_ms,
|
|
175
|
+
DEFAULT_MAX_POLL_INTERVAL_MS,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 图片插件给模型的最小使用说明。
|
|
181
|
+
*/
|
|
182
|
+
system(_context: AgentContext): string {
|
|
183
|
+
return [
|
|
184
|
+
"Image generation is available through the plugin_call tool.",
|
|
185
|
+
`Call plugin "${this.name}" action "generate" when the user asks to create, render, draw, or edit an image.`,
|
|
186
|
+
"Pass a JSON payload with prompt, optional size/aspect_ratio/quality/n, and optional provider_options.",
|
|
187
|
+
"The generated image files will be attached to the final assistant message automatically.",
|
|
188
|
+
].join("\n");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async generate_image(
|
|
192
|
+
input: ImagePluginInput,
|
|
193
|
+
): Promise<ImagePluginResult> {
|
|
194
|
+
const created = await this.image_create(input);
|
|
195
|
+
validate_created_job(created);
|
|
196
|
+
const deadline = Date.now() + this.timeout_ms;
|
|
197
|
+
let poll_after_ms = created.poll_after_ms;
|
|
198
|
+
|
|
199
|
+
while (Date.now() < deadline) {
|
|
200
|
+
await sleep(
|
|
201
|
+
clamp_poll_interval(
|
|
202
|
+
poll_after_ms,
|
|
203
|
+
this.min_poll_interval_ms,
|
|
204
|
+
this.max_poll_interval_ms,
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
const current = await this.image_result({ job_id: created.job_id });
|
|
208
|
+
validate_job_result(current);
|
|
209
|
+
poll_after_ms = current.poll_after_ms;
|
|
210
|
+
if (current.status === "succeeded") {
|
|
211
|
+
if (!current.result) {
|
|
212
|
+
throw new Error(`Image job ${created.job_id} succeeded without result`);
|
|
213
|
+
}
|
|
214
|
+
return normalize_image_result(current.result);
|
|
215
|
+
}
|
|
216
|
+
if (current.status === "failed") {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Image job failed: ${current.error ?? current.message ?? created.job_id}`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw new Error(`Image job timed out: ${created.job_id}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 显式 action 集合。
|
|
228
|
+
*/
|
|
229
|
+
readonly actions = {
|
|
230
|
+
generate: {
|
|
231
|
+
execute: async ({ payload }: { payload: JsonValue }) => {
|
|
232
|
+
try {
|
|
233
|
+
const input = normalize_image_payload(payload);
|
|
234
|
+
const message = await this.generate_image(input);
|
|
235
|
+
return {
|
|
236
|
+
success: true,
|
|
237
|
+
data: message as unknown as JsonObject,
|
|
238
|
+
message: "image generated",
|
|
239
|
+
};
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return {
|
|
242
|
+
success: false,
|
|
243
|
+
error: String(error),
|
|
244
|
+
message: String(error),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ImagePlugin 类型定义。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - 这里仅定义图片 plugin 对图片能力的最低层协议,不绑定 city 或任意上游 provider。
|
|
6
|
+
* - 图片生成结果使用 AI SDK UIMessage,保证 session 落盘格式与现有消息系统一致。
|
|
7
|
+
* - 字段保持 JSON 可序列化,便于通过 plugin action 与 tool bridge 传递。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { UIMessage } from "ai";
|
|
11
|
+
import type {
|
|
12
|
+
JsonObject,
|
|
13
|
+
JsonValue,
|
|
14
|
+
} from "@downcity/agent/internal/types/common/Json.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 图片生成文本内容片段。
|
|
18
|
+
*/
|
|
19
|
+
export interface ImagePluginTextContent {
|
|
20
|
+
/** 内容类型,固定为文本。 */
|
|
21
|
+
type: "text";
|
|
22
|
+
/** 生图提示词或上下文文本。 */
|
|
23
|
+
text: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 图片生成参考图片内容片段。
|
|
28
|
+
*/
|
|
29
|
+
export interface ImagePluginFileContent {
|
|
30
|
+
/** 内容类型,固定为图片。 */
|
|
31
|
+
type: "image";
|
|
32
|
+
/** 远程图片 URL。 */
|
|
33
|
+
url?: string;
|
|
34
|
+
/** data URL 图片内容。 */
|
|
35
|
+
data_url?: string;
|
|
36
|
+
/** 图片 MIME 类型,例如 `image/png`。 */
|
|
37
|
+
media_type?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 图片生成多模态内容片段。
|
|
42
|
+
*/
|
|
43
|
+
export type ImagePluginContent =
|
|
44
|
+
| ImagePluginTextContent
|
|
45
|
+
| ImagePluginFileContent;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 图片生成上下文消息。
|
|
49
|
+
*/
|
|
50
|
+
export interface ImagePluginMessage {
|
|
51
|
+
/** 消息角色。 */
|
|
52
|
+
role: "system" | "user" | "assistant";
|
|
53
|
+
/** 该消息内的文本与图片内容。 */
|
|
54
|
+
content: ImagePluginContent[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* ImagePlugin 调用输入。
|
|
59
|
+
*/
|
|
60
|
+
export interface ImagePluginInput {
|
|
61
|
+
/** 图片模型引用。 */
|
|
62
|
+
model?: string;
|
|
63
|
+
/** 单句快捷提示词。 */
|
|
64
|
+
prompt?: string;
|
|
65
|
+
/** 多轮或多模态图片生成上下文。 */
|
|
66
|
+
messages?: ImagePluginMessage[];
|
|
67
|
+
/** 生成图片数量。 */
|
|
68
|
+
n?: number;
|
|
69
|
+
/** 生成图片数量,兼容部分上游使用的 count 命名。 */
|
|
70
|
+
count?: number;
|
|
71
|
+
/** 图片尺寸,例如 `1024x1024`。 */
|
|
72
|
+
size?: string;
|
|
73
|
+
/** 图片宽高比,例如 `1:1`。 */
|
|
74
|
+
aspect_ratio?: string;
|
|
75
|
+
/** 图片宽高比,兼容部分上游使用的 ratio 命名。 */
|
|
76
|
+
ratio?: string;
|
|
77
|
+
/** 图片质量,例如 `standard`、`hd`、`ultra`、`4k`。 */
|
|
78
|
+
quality?: string;
|
|
79
|
+
/** 随机种子。 */
|
|
80
|
+
seed?: number;
|
|
81
|
+
/** 业务侧任务 ID,用于 provider 侧幂等、追踪和恢复。 */
|
|
82
|
+
client_job_id?: string;
|
|
83
|
+
/** Provider 私有参数,例如 `{ openai: {...}, gemini: {...}, luchi: {...} }`。 */
|
|
84
|
+
provider_options?: JsonObject;
|
|
85
|
+
/** 允许外部 image 函数接收其他 JSON 可序列化参数。 */
|
|
86
|
+
[key: string]: JsonValue | ImagePluginMessage[] | undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* ImagePlugin 生成结果。
|
|
91
|
+
*/
|
|
92
|
+
export type ImagePluginResult = UIMessage;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 图片生成任务状态。
|
|
96
|
+
*/
|
|
97
|
+
export type ImagePluginJobStatus =
|
|
98
|
+
| "queued"
|
|
99
|
+
| "running"
|
|
100
|
+
| "succeeded"
|
|
101
|
+
| "failed";
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 图片任务创建结果。
|
|
105
|
+
*/
|
|
106
|
+
export interface ImagePluginJobCreateResult {
|
|
107
|
+
/** 图片任务 ID。 */
|
|
108
|
+
job_id: string;
|
|
109
|
+
/** 创建后的任务状态。 */
|
|
110
|
+
status: ImagePluginJobStatus;
|
|
111
|
+
/** 建议下一次轮询的间隔毫秒数。 */
|
|
112
|
+
poll_after_ms?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 图片任务查询结果。
|
|
117
|
+
*/
|
|
118
|
+
export interface ImagePluginJobResult {
|
|
119
|
+
/** 图片任务 ID。 */
|
|
120
|
+
job_id: string;
|
|
121
|
+
/** 当前任务状态。 */
|
|
122
|
+
status: ImagePluginJobStatus;
|
|
123
|
+
/** 成功时返回的 AI SDK UIMessage。 */
|
|
124
|
+
result?: ImagePluginResult;
|
|
125
|
+
/** 失败时返回的错误消息。 */
|
|
126
|
+
error?: string;
|
|
127
|
+
/** 当前任务状态说明。 */
|
|
128
|
+
message?: string;
|
|
129
|
+
/** 建议下一次轮询的间隔毫秒数。 */
|
|
130
|
+
poll_after_ms?: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* ImagePlugin 构造参数。
|
|
135
|
+
*/
|
|
136
|
+
export interface ImagePluginOptions {
|
|
137
|
+
/** Plugin 稳定名称,默认 `image`。 */
|
|
138
|
+
name?: string;
|
|
139
|
+
/** Plugin 展示标题,默认 `Image`。 */
|
|
140
|
+
title?: string;
|
|
141
|
+
/** Plugin 用途说明。 */
|
|
142
|
+
description?: string;
|
|
143
|
+
/** 创建图片生成任务,通常传入 `(input) => city.ai.image_create(input)`。 */
|
|
144
|
+
image_create?: (
|
|
145
|
+
input: ImagePluginInput,
|
|
146
|
+
) => Promise<ImagePluginJobCreateResult> | ImagePluginJobCreateResult;
|
|
147
|
+
/** 查询图片生成任务,通常传入 `(input) => city.ai.image_result(input)`。 */
|
|
148
|
+
image_result?: (input: {
|
|
149
|
+
job_id: string;
|
|
150
|
+
}) => Promise<ImagePluginJobResult> | ImagePluginJobResult;
|
|
151
|
+
/** 图片任务最大等待时间,默认 300000ms。 */
|
|
152
|
+
timeout_ms?: number;
|
|
153
|
+
/** 轮询间隔下限,默认 100ms。 */
|
|
154
|
+
min_poll_interval_ms?: number;
|
|
155
|
+
/** 轮询间隔上限,默认 10000ms。 */
|
|
156
|
+
max_poll_interval_ms?: number;
|
|
157
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export {
|
|
|
13
13
|
} from "./BuiltinPlugins.js";
|
|
14
14
|
export type { BuiltinPluginClass } from "./BuiltinPlugins.js";
|
|
15
15
|
export { ChatPlugin } from "./chat/ChatPlugin.js";
|
|
16
|
+
export { ImagePlugin } from "./image/ImagePlugin.js";
|
|
16
17
|
export { ChatChannelAccountManager } from "./chat/accounts/ChannelAccountManager.js";
|
|
17
18
|
export { ChatAuthorizationPlugin } from "./auth/Plugin.js";
|
|
18
19
|
export { SkillPlugin } from "./skill/Plugin.js";
|
|
@@ -42,6 +43,18 @@ export type {
|
|
|
42
43
|
ChatPluginQqOptions,
|
|
43
44
|
ChatPluginTelegramOptions,
|
|
44
45
|
} from "./chat/ChatPluginTypes.js";
|
|
46
|
+
export type {
|
|
47
|
+
ImagePluginContent,
|
|
48
|
+
ImagePluginFileContent,
|
|
49
|
+
ImagePluginInput,
|
|
50
|
+
ImagePluginJobCreateResult,
|
|
51
|
+
ImagePluginJobResult,
|
|
52
|
+
ImagePluginJobStatus,
|
|
53
|
+
ImagePluginMessage,
|
|
54
|
+
ImagePluginOptions,
|
|
55
|
+
ImagePluginResult,
|
|
56
|
+
ImagePluginTextContent,
|
|
57
|
+
} from "./image/types/ImagePlugin.js";
|
|
45
58
|
export type {
|
|
46
59
|
ChatAuthorizationCatalog,
|
|
47
60
|
ChatAuthorizationChannel,
|
package/src/shell/ShellPlugin.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* - ShellActionRuntime 只保留纯运行时流程,不再承载模块级单例状态。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { AgentRuntime } from "@downcity/agent/internal/types/runtime/agent/AgentRuntime.js";
|
|
11
10
|
import { BasePlugin } from "@downcity/agent/internal/plugin/core/BasePlugin.js";
|
|
12
11
|
import type { PluginActions } from "@downcity/agent/internal/plugin/types/Plugin.js";
|
|
13
12
|
import type { AgentContext } from "@downcity/agent/internal/types/runtime/agent/AgentContext.js";
|
|
@@ -61,8 +60,8 @@ export class ShellPlugin extends BasePlugin {
|
|
|
61
60
|
*/
|
|
62
61
|
public readonly sessions: Map<string, ShellSessionRuntimeState>;
|
|
63
62
|
|
|
64
|
-
constructor(
|
|
65
|
-
super(
|
|
63
|
+
constructor() {
|
|
64
|
+
super();
|
|
66
65
|
this.state = createShellPluginState();
|
|
67
66
|
this.sessions = this.state.sessions;
|
|
68
67
|
this.actions = {
|
package/src/skill/Action.ts
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
import fs from "fs-extra";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { discoverClaudeSkillsSync } from "@/skill/runtime/Discovery.js";
|
|
12
|
-
import { loadDowncityConfig } from "@downcity/agent/internal/config/Config.js";
|
|
13
12
|
import type { ClaudeSkill } from "@/skill/types/ClaudeSkill.js";
|
|
14
13
|
import type { JsonValue } from "@downcity/agent/internal/types/common/Json.js";
|
|
15
14
|
import type {
|
|
@@ -18,6 +17,7 @@ import type {
|
|
|
18
17
|
SkillLookupResponse,
|
|
19
18
|
SkillSummary,
|
|
20
19
|
} from "@/skill/types/SkillCommand.js";
|
|
20
|
+
import type { SkillPluginOptions } from "@/skill/types/SkillPlugin.js";
|
|
21
21
|
|
|
22
22
|
function normalizeAllowedTools(input: JsonValue | undefined): string[] {
|
|
23
23
|
if (!Array.isArray(input)) return [];
|
|
@@ -64,10 +64,12 @@ function findSkill(skills: ClaudeSkill[], name: string): ClaudeSkill | null {
|
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function getSkills(
|
|
67
|
+
function getSkills(
|
|
68
|
+
projectRoot: string,
|
|
69
|
+
options?: SkillPluginOptions | null,
|
|
70
|
+
): ClaudeSkill[] {
|
|
68
71
|
const root = path.resolve(projectRoot);
|
|
69
|
-
|
|
70
|
-
return discoverClaudeSkillsSync(root, config);
|
|
72
|
+
return discoverClaudeSkillsSync(root, options);
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/**
|
|
@@ -76,8 +78,9 @@ function getSkills(projectRoot: string): ClaudeSkill[] {
|
|
|
76
78
|
export function findLearnedSkillExact(
|
|
77
79
|
projectRoot: string,
|
|
78
80
|
query: string,
|
|
81
|
+
options?: SkillPluginOptions | null,
|
|
79
82
|
): SkillSummary | null {
|
|
80
|
-
const skills = getSkills(projectRoot);
|
|
83
|
+
const skills = getSkills(projectRoot, options);
|
|
81
84
|
const target = findSkillExact(skills, query);
|
|
82
85
|
return target ? toSkillSummary(target) : null;
|
|
83
86
|
}
|
|
@@ -89,11 +92,12 @@ export function searchLearnedSkills(
|
|
|
89
92
|
projectRoot: string,
|
|
90
93
|
query: string,
|
|
91
94
|
limit: number = 10,
|
|
95
|
+
options?: SkillPluginOptions | null,
|
|
92
96
|
): SkillSummary[] {
|
|
93
97
|
const q = String(query || "").trim().toLowerCase();
|
|
94
98
|
if (!q) return [];
|
|
95
99
|
|
|
96
|
-
const skills = getSkills(projectRoot);
|
|
100
|
+
const skills = getSkills(projectRoot, options);
|
|
97
101
|
const matched = skills.filter((item) => {
|
|
98
102
|
const id = item.id.toLowerCase();
|
|
99
103
|
const name = item.name.toLowerCase();
|
|
@@ -107,8 +111,11 @@ export function searchLearnedSkills(
|
|
|
107
111
|
/**
|
|
108
112
|
* 列出当前项目下可发现的全部 skill。
|
|
109
113
|
*/
|
|
110
|
-
export function listSkills(
|
|
111
|
-
|
|
114
|
+
export function listSkills(
|
|
115
|
+
projectRoot: string,
|
|
116
|
+
options?: SkillPluginOptions | null,
|
|
117
|
+
): SkillListResponse {
|
|
118
|
+
const skills = getSkills(projectRoot, options).map(toSkillSummary);
|
|
112
119
|
return {
|
|
113
120
|
success: true,
|
|
114
121
|
skills,
|
|
@@ -127,9 +134,13 @@ export async function lookupSkill(params: {
|
|
|
127
134
|
* skill lookup 请求。
|
|
128
135
|
*/
|
|
129
136
|
request: SkillLookupRequest;
|
|
137
|
+
/**
|
|
138
|
+
* SkillPlugin 构造参数。
|
|
139
|
+
*/
|
|
140
|
+
options?: SkillPluginOptions | null;
|
|
130
141
|
}): Promise<SkillLookupResponse> {
|
|
131
142
|
const root = path.resolve(params.projectRoot);
|
|
132
|
-
const skills = getSkills(root);
|
|
143
|
+
const skills = getSkills(root, params.options);
|
|
133
144
|
const target = findSkill(skills, params.request.name);
|
|
134
145
|
if (!target) {
|
|
135
146
|
return {
|
package/src/skill/Command.ts
CHANGED
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 设计目标(中文)
|
|
5
5
|
* - 尽量不自建 registry:直接复用社区的 `npx skills` 生态(find/install)。
|
|
6
|
-
* - 同时提供本地视角的 `list
|
|
6
|
+
* - 同时提供本地视角的 `list`:按 SkillPlugin 默认构造参数列出当前项目 skills。
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import path from "node:path";
|
|
10
|
-
import fs from "fs-extra";
|
|
11
10
|
import { execa } from "execa";
|
|
12
11
|
import { discoverClaudeSkillsSync } from "@/skill/runtime/Discovery.js";
|
|
13
12
|
import { getClaudeSkillSearchRoots } from "@/skill/runtime/Paths.js";
|
|
14
|
-
import { loadDowncityConfig } from "@downcity/agent/internal/config/Config.js";
|
|
15
13
|
|
|
16
14
|
async function runNpxSkills(args: string[], opts?: { yes?: boolean }): Promise<number> {
|
|
17
15
|
const yes = opts?.yes !== false;
|
|
@@ -76,16 +74,8 @@ export async function skillInstallCommand(
|
|
|
76
74
|
*/
|
|
77
75
|
export async function skillListCommand(cwd: string = "."): Promise<void> {
|
|
78
76
|
const projectRoot = path.resolve(String(cwd || "."));
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
throw new Error(
|
|
82
|
-
`downcity.json not found at ${shipJson}. Run "town agent create" first or pass the correct path.`,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const config = loadDowncityConfig(projectRoot);
|
|
87
|
-
const roots = getClaudeSkillSearchRoots(projectRoot, config);
|
|
88
|
-
const skills = discoverClaudeSkillsSync(projectRoot, config);
|
|
77
|
+
const roots = getClaudeSkillSearchRoots(projectRoot);
|
|
78
|
+
const skills = discoverClaudeSkillsSync(projectRoot);
|
|
89
79
|
|
|
90
80
|
console.log("Skill roots:");
|
|
91
81
|
for (const root of roots) console.log(`- [${root.source}] ${root.display}`);
|
package/src/skill/Config.ts
CHANGED
|
@@ -1,51 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* SkillPlugin 构造参数归一化工具。
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
5
|
+
* - SkillPlugin 不再读取 `downcity.json.plugins.skill` 私有配置。
|
|
6
|
+
* - constructor options 是唯一行为配置入口,便于 SDK 用户直接理解。
|
|
7
|
+
* - 这里只做默认值与去重,不做文件系统扫描。
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import type { DowncityConfig } from "@downcity/agent/internal/types/config/DowncityConfig.js";
|
|
10
10
|
import type {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
ResolvedSkillPluginOptions,
|
|
12
|
+
SkillPluginOptions,
|
|
13
13
|
} from "@/skill/types/SkillPlugin.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* skill plugin
|
|
16
|
+
* skill plugin 默认构造参数。
|
|
17
17
|
*/
|
|
18
|
-
export const
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
export const DEFAULT_SKILL_PLUGIN_OPTIONS: ResolvedSkillPluginOptions = {
|
|
19
|
+
use: ["project"],
|
|
20
|
+
paths: [],
|
|
21
|
+
ignore: [],
|
|
21
22
|
};
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
function normalizeUse(
|
|
25
|
+
input: SkillPluginOptions["use"],
|
|
26
|
+
): ResolvedSkillPluginOptions["use"] {
|
|
27
|
+
if (!Array.isArray(input)) return [...DEFAULT_SKILL_PLUGIN_OPTIONS.use];
|
|
28
|
+
const values: ResolvedSkillPluginOptions["use"] = [];
|
|
29
|
+
for (const item of input) {
|
|
30
|
+
if ((item === "project" || item === "home") && !values.includes(item)) {
|
|
31
|
+
values.push(item);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return values;
|
|
35
|
+
}
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
function normalizePaths(input: SkillPluginOptions["paths"]): string[] {
|
|
38
|
+
if (!Array.isArray(input)) return [...DEFAULT_SKILL_PLUGIN_OPTIONS.paths];
|
|
39
|
+
const values: string[] = [];
|
|
40
|
+
for (const item of input) {
|
|
41
|
+
const value = String(item || "").trim();
|
|
42
|
+
if (value && !values.includes(value)) values.push(value);
|
|
43
|
+
}
|
|
44
|
+
return values;
|
|
45
|
+
}
|
|
40
46
|
|
|
47
|
+
/**
|
|
48
|
+
* 读取并归一化 SkillPlugin 构造参数。
|
|
49
|
+
*/
|
|
50
|
+
export function resolveSkillPluginOptions(
|
|
51
|
+
options?: SkillPluginOptions | null,
|
|
52
|
+
): ResolvedSkillPluginOptions {
|
|
41
53
|
return {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
: [...DEFAULT_SKILL_PLUGIN_CONFIG.paths],
|
|
46
|
-
allowExternalPaths:
|
|
47
|
-
typeof skillConfig?.allowExternalPaths === "boolean"
|
|
48
|
-
? skillConfig.allowExternalPaths
|
|
49
|
-
: DEFAULT_SKILL_PLUGIN_CONFIG.allowExternalPaths,
|
|
54
|
+
use: normalizeUse(options?.use),
|
|
55
|
+
paths: normalizePaths(options?.paths),
|
|
56
|
+
ignore: Array.isArray(options?.ignore) ? [...options.ignore] : [],
|
|
50
57
|
};
|
|
51
58
|
}
|
package/src/skill/PROMPT.ts
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// Source: src/skill/PROMPT.ts.txt
|
|
7
|
-
const TEXT_MODULE_CONTENT = "# Skill Plugin\n\nskill 是你拥有的技能。用户的提出的需求和任务,你都需要先考虑利用相关的skill来高质量的完成。\n\n\n## 使用\n\n使用技能时需要首先通过 `
|
|
7
|
+
const TEXT_MODULE_CONTENT = "# Skill Plugin\n\nskill 是你拥有的技能。用户的提出的需求和任务,你都需要先考虑利用相关的skill来高质量的完成。\n\n\n## 使用\n\n使用技能时需要首先通过 `skill` plugin 的 `lookup` action 载入对应的技能内容。\n\n如果当前工具集中存在 `plugin_call`,使用:\n\n```ts\nplugin_call({\n plugin: \"skill\",\n action: \"lookup\",\n payload: {\n name: \"<skill-name>\",\n },\n});\n```\n\n可用 action 包括 `list`、`find`、`install`、`lookup`。\n";
|
|
8
8
|
|
|
9
9
|
export default TEXT_MODULE_CONTENT;
|