koishi-plugin-dify-ai 0.1.7 → 0.1.9

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/lib/config.d.ts CHANGED
@@ -52,6 +52,9 @@ export type VariableEntry = {
52
52
  source: 'constant';
53
53
  field: string;
54
54
  value: string;
55
+ } | {
56
+ source: 'json';
57
+ template: string;
55
58
  };
56
59
  /** 作用域的一行:维度 + 值 */
57
60
  export interface ScopeRow {
@@ -111,8 +114,8 @@ export interface Config {
111
114
  }
112
115
  export declare const Config: Schema<Config>;
113
116
  /**
114
- * 上传消息文件配置(含 0.1.x 布尔遗留形态迁移):
115
- * 数量与单文件大小 0 均为不限,大小仍与 Dify 各类型上限取较小值。
117
+ * 上传消息文件配置(含历史形态迁移:0.1.x 布尔、0.1.7 mode)。
118
+ * 数量与单文件大小 0 与 -1 均为不限,大小仍与 Dify 各类型上限取较小值。
116
119
  */
117
120
  export declare function resolveUploadConfig(config: Config): {
118
121
  enabled: boolean;
@@ -120,9 +123,9 @@ export declare function resolveUploadConfig(config: Config): {
120
123
  maxMb: number;
121
124
  };
122
125
  /**
123
- * 引用触发消息配置(含 0.1.x 布尔开关迁移):schema 会给旧配置填默认 quote 对象,
124
- * 因此迁移判据是「旧布尔被配置层透传保留 且 quote 仍是默认形态(用户未在新表单改过)」——
125
- * 此时以旧布尔为准;用户改过新表单(quote 非默认)则新值优先。
126
+ * 引用触发消息配置(含历史形态迁移:0.1.x 布尔与 quoteTrigger 透传、0.1.7 mode、布尔开关新形态)。
127
+ * 迁移判据沿用:旧布尔被透传保留且配置未被用户改动(默认形态)时以旧布尔为准。
128
+ * 阈值 -1 与 0 同为总是引用(比较 elapsed >= 阈值 自然兼容)。
126
129
  */
127
130
  export declare function resolveQuoteConfig(config: Config): {
128
131
  enabled: boolean;
package/lib/index.js CHANGED
@@ -49,6 +49,15 @@ var en_US_default = {
49
49
 
50
50
  // src/config.ts
51
51
  var import_koishi = require("koishi");
52
+
53
+ // src/types.ts
54
+ function isMessageTrigger(type) {
55
+ return type !== "schedule" && type !== "density";
56
+ }
57
+ __name(isMessageTrigger, "isMessageTrigger");
58
+ var JSON_PLACEHOLDER = /\{\{\s*(\w+)(?::(\d+))?\s*\}\}/g;
59
+
60
+ // src/config.ts
52
61
  var appTypeSchema = import_koishi.Schema.union([
53
62
  import_koishi.Schema.const("auto").description("自动探测"),
54
63
  import_koishi.Schema.const("chat").description("Chatbot"),
@@ -83,7 +92,7 @@ var conditionSchema = import_koishi.Schema.union([
83
92
  import_koishi.Schema.object({
84
93
  type: import_koishi.Schema.const("schedule").required(),
85
94
  cron: import_koishi.Schema.string().default("").description("Cron 表达式"),
86
- intervalMinutes: import_koishi.Schema.number().default(0).min(0).description("间隔分钟")
95
+ intervalMinutes: import_koishi.Schema.number().default(-1).min(-1).description("间隔分钟")
87
96
  }).default({ type: "schedule", cron: "", intervalMinutes: 0 }).description("定时"),
88
97
  import_koishi.Schema.object({
89
98
  type: import_koishi.Schema.const("density").required(),
@@ -121,7 +130,7 @@ var variableSchema = import_koishi.Schema.union([
121
130
  import_koishi.Schema.object({
122
131
  source: import_koishi.Schema.const("history").required(),
123
132
  field: import_koishi.Schema.string().required().description("字段名"),
124
- limit: import_koishi.Schema.natural().default(0).min(0).max(100).description("条数,0 用全局")
133
+ limit: import_koishi.Schema.number().default(-1).min(-1).max(100).description("条数,0 用全局")
125
134
  }).default({ source: "history", field: "", limit: 0 }).description("历史消息"),
126
135
  import_koishi.Schema.object({
127
136
  source: import_koishi.Schema.const("meta").required(),
@@ -132,17 +141,32 @@ var variableSchema = import_koishi.Schema.union([
132
141
  source: import_koishi.Schema.const("constant").required(),
133
142
  field: import_koishi.Schema.string().required().description("字段名"),
134
143
  value: import_koishi.Schema.string().role("textarea").required().description("固定文本")
135
- }).default({ source: "constant", field: "", value: "" }).description("常量文本")
144
+ }).default({ source: "constant", field: "", value: "" }).description("常量文本"),
145
+ import_koishi.Schema.object({
146
+ source: import_koishi.Schema.const("json").required(),
147
+ template: import_koishi.Schema.string().role("textarea").required().description("JSON 对象")
148
+ }).default({ source: "json", template: "" }).description("JSON 对象")
136
149
  ]);
137
150
  var quoteSchema = import_koishi.Schema.union([
138
- import_koishi.Schema.object({
139
- mode: import_koishi.Schema.const("off").required()
140
- }).default({ mode: "off" }).description("关闭"),
151
+ import_koishi.Schema.intersect([
152
+ import_koishi.Schema.object({
153
+ enabled: import_koishi.Schema.boolean().default(true).description("开启")
154
+ }),
155
+ import_koishi.Schema.union([
156
+ import_koishi.Schema.object({
157
+ enabled: import_koishi.Schema.const(true).required(),
158
+ minElapsedSeconds: import_koishi.Schema.number().min(-1).default(-1).description("回复耗时多少秒才引用,0 总是引用")
159
+ }),
160
+ import_koishi.Schema.object({})
161
+ ])
162
+ ]),
163
+ import_koishi.Schema.boolean(),
164
+ import_koishi.Schema.object({ mode: import_koishi.Schema.const("off").required() }),
141
165
  import_koishi.Schema.object({
142
166
  mode: import_koishi.Schema.const("on").required(),
143
- minElapsedSeconds: import_koishi.Schema.natural().default(0).description("回复耗时多少秒才引用,0 总是引用")
144
- }).default({ mode: "on", minElapsedSeconds: 0 }).description("开启")
145
- ]);
167
+ minElapsedSeconds: import_koishi.Schema.number().min(-1).default(-1)
168
+ })
169
+ ]).default({ enabled: true, minElapsedSeconds: -1 });
146
170
  var cooldownSchema = import_koishi.Schema.union([
147
171
  import_koishi.Schema.object({
148
172
  mode: import_koishi.Schema.const("off").required()
@@ -158,16 +182,27 @@ var cooldownSchema = import_koishi.Schema.union([
158
182
  }).default({ mode: "sliding", minSeconds: 30, maxSeconds: 120 }).description("滑动窗口")
159
183
  ]);
160
184
  var uploadSchema = import_koishi.Schema.union([
161
- import_koishi.Schema.object({
162
- mode: import_koishi.Schema.const("off").required()
163
- }).default({ mode: "off" }).description("关闭"),
185
+ import_koishi.Schema.intersect([
186
+ import_koishi.Schema.object({
187
+ enabled: import_koishi.Schema.boolean().default(true).description("开启")
188
+ }),
189
+ import_koishi.Schema.union([
190
+ import_koishi.Schema.object({
191
+ enabled: import_koishi.Schema.const(true).required(),
192
+ maxCount: import_koishi.Schema.number().min(-1).default(-1).description("数量上限,0 不限"),
193
+ maxSizeMb: import_koishi.Schema.number().min(-1).default(-1).description("单文件大小上限 MB,0 不限")
194
+ }),
195
+ import_koishi.Schema.object({})
196
+ ])
197
+ ]),
198
+ import_koishi.Schema.boolean(),
199
+ import_koishi.Schema.object({ mode: import_koishi.Schema.const("off").required() }),
164
200
  import_koishi.Schema.object({
165
201
  mode: import_koishi.Schema.const("on").required(),
166
- maxCount: import_koishi.Schema.natural().default(0).description("数量上限,0 不限"),
167
- maxSizeMb: import_koishi.Schema.natural().default(0).description("单文件大小上限 MB,0 不限")
168
- }).default({ mode: "on", maxCount: 0, maxSizeMb: 0 }).description("开启"),
169
- import_koishi.Schema.boolean().description("旧版开关")
170
- ]);
202
+ maxCount: import_koishi.Schema.number().min(-1).default(-1),
203
+ maxSizeMb: import_koishi.Schema.number().min(-1).default(-1)
204
+ })
205
+ ]).default({ enabled: true, maxCount: -1, maxSizeMb: -1 });
171
206
  var Config = import_koishi.Schema.intersect([
172
207
  import_koishi.Schema.object({
173
208
  apps: import_koishi.Schema.array(
@@ -185,7 +220,7 @@ var Config = import_koishi.Schema.intersect([
185
220
  import_koishi.Schema.object({
186
221
  name: import_koishi.Schema.string().required().description("规则名"),
187
222
  enabled: import_koishi.Schema.boolean().default(true).description("启用"),
188
- priority: import_koishi.Schema.number().default(0).description("优先级,大者先匹配"),
223
+ priority: import_koishi.Schema.number().default(-1).description("优先级,大者先匹配"),
189
224
  app: import_koishi.Schema.string().required().description("应用名"),
190
225
  conversationScope: import_koishi.Schema.union([
191
226
  import_koishi.Schema.const("per-guild").description("按群聊"),
@@ -214,9 +249,9 @@ var Config = import_koishi.Schema.intersect([
214
249
  }).description("触发规则"),
215
250
  import_koishi.Schema.object({
216
251
  parseElements: import_koishi.Schema.boolean().default(false).description("解析回复中的消息元素"),
217
- quote: quoteSchema.default({ mode: "on", minElapsedSeconds: 0 }).description("引用触发消息"),
218
- maxSegmentLength: import_koishi.Schema.natural().default(0).description("分段长度,0 不分段"),
219
- uploadFiles: uploadSchema.default({ mode: "on", maxCount: 0, maxSizeMb: 0 }).description("上传消息中的文件")
252
+ quote: quoteSchema.description("引用触发消息"),
253
+ maxSegmentLength: import_koishi.Schema.number().min(-1).default(-1).description("分段长度,0 不分段"),
254
+ uploadFiles: uploadSchema.description("上传消息中的文件")
220
255
  }).description("回复行为"),
221
256
  import_koishi.Schema.object({
222
257
  requestTimeout: import_koishi.Schema.natural().role("ms").default(3e5).description("请求超时"),
@@ -309,6 +344,17 @@ function toScope(rows) {
309
344
  }
310
345
  __name(toScope, "toScope");
311
346
  function toVariable(entry) {
347
+ if (entry.source === "json") {
348
+ const template = entry.template ?? "";
349
+ if (!template.trim()) return null;
350
+ try {
351
+ const probe = JSON.parse(template.replace(JSON_PLACEHOLDER, "0"));
352
+ if (typeof probe !== "object" || probe === null || Array.isArray(probe)) return null;
353
+ } catch {
354
+ return null;
355
+ }
356
+ return { field: "", source: { type: "json", template } };
357
+ }
312
358
  const field = entry.field?.trim();
313
359
  if (!field) return null;
314
360
  if (entry.source === "constant") {
@@ -328,25 +374,30 @@ function toVariable(entry) {
328
374
  }
329
375
  __name(toVariable, "toVariable");
330
376
  function resolveUploadConfig(config) {
331
- if (typeof config.uploadFiles === "boolean") {
332
- return config.uploadFiles ? { enabled: true, maxCount: 0, maxMb: 0 } : { enabled: false, maxCount: 0, maxMb: 0 };
377
+ const upload = config.uploadFiles;
378
+ if (typeof upload === "boolean") {
379
+ return upload ? { enabled: true, maxCount: 0, maxMb: 0 } : { enabled: false, maxCount: 0, maxMb: 0 };
380
+ }
381
+ if (upload?.mode === "off" || upload?.enabled === false) {
382
+ return { enabled: false, maxCount: 0, maxMb: 0 };
333
383
  }
334
- if (config.uploadFiles?.mode === "off") return { enabled: false, maxCount: 0, maxMb: 0 };
335
384
  return {
336
385
  enabled: true,
337
- maxCount: Number(config.uploadFiles?.maxCount) || 0,
338
- maxMb: Number(config.uploadFiles?.maxSizeMb) || 0
386
+ maxCount: Number(upload?.maxCount) || 0,
387
+ maxMb: Number(upload?.maxSizeMb) || 0
339
388
  };
340
389
  }
341
390
  __name(resolveUploadConfig, "resolveUploadConfig");
342
391
  function resolveQuoteConfig(config) {
343
392
  const quote = config.quote;
344
- const isDefaultQuote = !quote || quote.mode === "on" && !(Number(quote.minElapsedSeconds) > 0);
345
- if (typeof config.quoteTrigger === "boolean" && isDefaultQuote) {
393
+ if (typeof quote === "boolean") return { enabled: quote, minElapsedSeconds: 0 };
394
+ if (quote?.mode === "off") return { enabled: false, minElapsedSeconds: 0 };
395
+ const threshold = Number(quote?.minElapsedSeconds) || 0;
396
+ const untouched = !(threshold > 0) && quote?.enabled !== false;
397
+ if (typeof config.quoteTrigger === "boolean" && untouched) {
346
398
  return { enabled: config.quoteTrigger, minElapsedSeconds: 0 };
347
399
  }
348
- if (quote?.mode === "off") return { enabled: false, minElapsedSeconds: 0 };
349
- return { enabled: true, minElapsedSeconds: Number(quote?.minElapsedSeconds) || 0 };
400
+ return { enabled: quote?.mode === "on" ? true : quote?.enabled !== false, minElapsedSeconds: threshold };
350
401
  }
351
402
  __name(resolveQuoteConfig, "resolveQuoteConfig");
352
403
  function toCooldown(entry) {
@@ -1198,12 +1249,6 @@ var TriggerEngine = class {
1198
1249
  }
1199
1250
  };
1200
1251
 
1201
- // src/types.ts
1202
- function isMessageTrigger(type) {
1203
- return type !== "schedule" && type !== "density";
1204
- }
1205
- __name(isMessageTrigger, "isMessageTrigger");
1206
-
1207
1252
  // src/requester/files.ts
1208
1253
  var import_promises = require("fs/promises");
1209
1254
  var import_fs = require("fs");
@@ -1466,11 +1511,55 @@ function resolveSource(source, fireCtx, history, now) {
1466
1511
  }
1467
1512
  }
1468
1513
  __name(resolveSource, "resolveSource");
1514
+ var META_PLACEHOLDERS = /* @__PURE__ */ new Set(["triggerType", "ruleName", "triggerTime"]);
1515
+ function resolvePlaceholderValue(name2, limit, fireCtx, history, now) {
1516
+ if (name2 === "history") {
1517
+ const channelKey = channelKeyOf(fireCtx.platform, fireCtx.channelId);
1518
+ return history.toVariable(history.get(channelKey, limit ?? history.limit));
1519
+ }
1520
+ if (name2 === "isDirect") {
1521
+ return fireCtx.session?.isDirect ?? fireCtx.isDirect ?? false;
1522
+ }
1523
+ if (META_PLACEHOLDERS.has(name2)) {
1524
+ return String(resolveMetaField(name2, fireCtx, now) ?? "");
1525
+ }
1526
+ const value = resolveSessionField(name2, fireCtx, now);
1527
+ return value === void 0 || value === null ? "" : String(value);
1528
+ }
1529
+ __name(resolvePlaceholderValue, "resolvePlaceholderValue");
1530
+ var BARE_PLACEHOLDER = /"\{\{\s*(\w+)(?::(\d+))?\s*\}\}"/g;
1531
+ var escapeJsonText = /* @__PURE__ */ __name((value) => JSON.stringify(value).slice(1, -1), "escapeJsonText");
1532
+ function interpolateTemplate(template, fireCtx, history, now) {
1533
+ const resolve = /* @__PURE__ */ __name((name2, limit) => resolvePlaceholderValue(name2, limit ? Number(limit) : void 0, fireCtx, history, now), "resolve");
1534
+ const native = template.replace(
1535
+ BARE_PLACEHOLDER,
1536
+ (_match, name2, limit) => JSON.stringify(resolve(name2, limit))
1537
+ );
1538
+ return native.replace(
1539
+ JSON_PLACEHOLDER,
1540
+ (_match, name2, limit) => escapeJsonText(String(resolve(name2, limit)))
1541
+ );
1542
+ }
1543
+ __name(interpolateTemplate, "interpolateTemplate");
1544
+ function resolveJsonObject(template, fireCtx, history, now) {
1545
+ try {
1546
+ const parsed = JSON.parse(interpolateTemplate(template, fireCtx, history, now));
1547
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
1548
+ } catch {
1549
+ return null;
1550
+ }
1551
+ }
1552
+ __name(resolveJsonObject, "resolveJsonObject");
1469
1553
  function resolveVariables(rule, fireCtx, history) {
1470
1554
  const inputs = {};
1471
1555
  const now = (/* @__PURE__ */ new Date()).toISOString();
1472
1556
  for (const binding of rule.variables) {
1473
- if (!binding?.field) continue;
1557
+ if (binding?.source?.type !== "json") continue;
1558
+ const object = resolveJsonObject(binding.source.template, fireCtx, history, now);
1559
+ if (object) Object.assign(inputs, object);
1560
+ }
1561
+ for (const binding of rule.variables) {
1562
+ if (!binding?.field || binding.source?.type === "json") continue;
1474
1563
  const value = resolveSource(binding.source, fireCtx, history, now);
1475
1564
  if (value !== void 0) inputs[binding.field] = value;
1476
1565
  }
@@ -1869,6 +1958,7 @@ var name = "dify-ai";
1869
1958
  var inject = { required: ["database"] };
1870
1959
  var PROBE_TIMEOUT_MS = 1e4;
1871
1960
  function apply(ctx, config) {
1961
+ const startedAt = Date.now();
1872
1962
  ctx.i18n.define("zh-CN", zh_CN_default);
1873
1963
  ctx.i18n.define("en-US", en_US_default);
1874
1964
  defineTables(ctx);
@@ -1880,7 +1970,11 @@ function apply(ctx, config) {
1880
1970
  const engine = new TriggerEngine(ctx, runner, history, rules, cooldown);
1881
1971
  ctx.on("ready", () => {
1882
1972
  engine.start();
1883
- void probeAppTypes(ctx, apps, appTypes);
1973
+ ctx.logger.info("插件装配完成,耗时 %dms", Date.now() - startedAt);
1974
+ const probeStart = Date.now();
1975
+ void probeAppTypes(ctx, apps, appTypes).then(() => {
1976
+ ctx.logger.info("应用类型探测完成,耗时 %dms", Date.now() - probeStart);
1977
+ });
1884
1978
  });
1885
1979
  const admin = ctx.command("dify", { authority: 3 });
1886
1980
  registerMiscCommands(admin, conversationStore);
@@ -1,5 +1,9 @@
1
1
  import { FireContext } from '../types';
2
2
  import { MessageHistory } from '../history';
3
3
  import { DifyRule } from '../types';
4
- /** 按规则的变量映射表构造 Dify inputs */
4
+ /**
5
+ * 按规则的变量映射表构造 Dify inputs。
6
+ * JSON 对象条目先注入(顶层键展开合并进 inputs),单条映射后注入覆盖同名键——
7
+ * 现有单条配置行为不变,JSON 作为批量默认值。
8
+ */
5
9
  export declare function resolveVariables(rule: DifyRule, fireCtx: FireContext, history: MessageHistory): Record<string, any>;
package/lib/types.d.ts CHANGED
@@ -68,7 +68,12 @@ export type VariableSource = {
68
68
  } | {
69
69
  type: 'constant';
70
70
  value: string;
71
+ } | {
72
+ type: 'json';
73
+ template: string;
71
74
  };
75
+ /** JSON 对象模板里的占位符:{{变量名}} 或 {{history:条数}}(加载校验与运行时插值共用) */
76
+ export declare const JSON_PLACEHOLDER: RegExp;
72
77
  /** 一条变量映射:Dify inputs 字段名 ← 来源 */
73
78
  export interface VariableBinding {
74
79
  field: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-dify-ai",
3
3
  "description": "Invoke Dify apps (chatflow / workflow / completion) in Koishi with triggers and conversation bindings",
4
- "version": "0.1.7",
4
+ "version": "0.1.9",
5
5
  "author": "PinkElysia",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/PinkElysiaDev/dify-ai-plugin-for-Koishi#readme",