chattercatcher 0.1.26 → 0.1.28
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/dist/cli.js +229 -48
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +216 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { input, password, select, confirm, number } from "@inquirer/prompts";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import
|
|
6
|
+
import fs15 from "fs/promises";
|
|
7
7
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "chattercatcher",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.28",
|
|
12
12
|
description: "\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u5E93\u673A\u5668\u4EBA",
|
|
13
13
|
type: "module",
|
|
14
14
|
main: "dist/index.js",
|
|
@@ -548,12 +548,17 @@ function migrateDatabase(database) {
|
|
|
548
548
|
next_run_at TEXT NOT NULL,
|
|
549
549
|
last_error TEXT,
|
|
550
550
|
created_at TEXT NOT NULL,
|
|
551
|
-
updated_at TEXT NOT NULL
|
|
551
|
+
updated_at TEXT NOT NULL,
|
|
552
|
+
image_file_name TEXT
|
|
552
553
|
);
|
|
553
554
|
|
|
554
555
|
CREATE INDEX IF NOT EXISTS cron_jobs_chat_status_idx ON cron_jobs(chat_id, status, updated_at);
|
|
555
556
|
CREATE INDEX IF NOT EXISTS cron_jobs_due_idx ON cron_jobs(status, next_run_at);
|
|
556
557
|
`);
|
|
558
|
+
const cronJobColumns = database.prepare("PRAGMA table_info(cron_jobs)").all();
|
|
559
|
+
if (!cronJobColumns.some((column) => column.name === "image_file_name")) {
|
|
560
|
+
database.prepare("ALTER TABLE cron_jobs ADD COLUMN image_file_name TEXT").run();
|
|
561
|
+
}
|
|
557
562
|
}
|
|
558
563
|
|
|
559
564
|
// src/doctor/checks.ts
|
|
@@ -966,6 +971,7 @@ function getGatewayStatus(config, secrets) {
|
|
|
966
971
|
}
|
|
967
972
|
|
|
968
973
|
// src/llm/openai-compatible.ts
|
|
974
|
+
var OPENAI_EMBEDDING_BATCH_SIZE = 64;
|
|
969
975
|
function normalizeBaseUrl(baseUrl) {
|
|
970
976
|
return baseUrl.replace(/\/+$/, "");
|
|
971
977
|
}
|
|
@@ -997,12 +1003,66 @@ function toOpenAITool(tool) {
|
|
|
997
1003
|
}
|
|
998
1004
|
};
|
|
999
1005
|
}
|
|
1006
|
+
function parseToolCallArguments(value) {
|
|
1007
|
+
try {
|
|
1008
|
+
return JSON.parse(value);
|
|
1009
|
+
} catch {
|
|
1010
|
+
return {};
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
function decodeDsmlValue(value, isString) {
|
|
1014
|
+
const trimmed = value.trim();
|
|
1015
|
+
if (isString) {
|
|
1016
|
+
return trimmed;
|
|
1017
|
+
}
|
|
1018
|
+
if (trimmed === "true") return true;
|
|
1019
|
+
if (trimmed === "false") return false;
|
|
1020
|
+
if (trimmed === "null") return null;
|
|
1021
|
+
const numberValue = Number(trimmed);
|
|
1022
|
+
if (trimmed && Number.isFinite(numberValue)) {
|
|
1023
|
+
return numberValue;
|
|
1024
|
+
}
|
|
1025
|
+
return trimmed;
|
|
1026
|
+
}
|
|
1027
|
+
function parseDsmlToolCalls(content) {
|
|
1028
|
+
if (!content?.includes("DSML")) {
|
|
1029
|
+
return [];
|
|
1030
|
+
}
|
|
1031
|
+
const toolCalls = [];
|
|
1032
|
+
const invokePattern = /<||DSML||invoke\s+name="([^"]+)"\s*>([\s\S]*?)<\/||DSML||invoke>/g;
|
|
1033
|
+
const parameterPattern = /<||DSML||parameter\s+name="([^"]+)"\s+string="(true|false)"\s*>([\s\S]*?)<\/||DSML||parameter>/g;
|
|
1034
|
+
for (const invoke of content.matchAll(invokePattern)) {
|
|
1035
|
+
const name = invoke[1];
|
|
1036
|
+
if (!name) {
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
const input2 = {};
|
|
1040
|
+
const body = invoke[2] ?? "";
|
|
1041
|
+
for (const parameter of body.matchAll(parameterPattern)) {
|
|
1042
|
+
const parameterName = parameter[1];
|
|
1043
|
+
if (!parameterName) {
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
input2[parameterName] = decodeDsmlValue(parameter[3] ?? "", parameter[2] === "true");
|
|
1047
|
+
}
|
|
1048
|
+
toolCalls.push({
|
|
1049
|
+
id: `dsml_${toolCalls.length + 1}`,
|
|
1050
|
+
name,
|
|
1051
|
+
input: input2
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
return toolCalls;
|
|
1055
|
+
}
|
|
1000
1056
|
function parseToolCalls(message) {
|
|
1001
|
-
|
|
1057
|
+
const standardToolCalls = message?.tool_calls?.map((toolCall) => ({
|
|
1002
1058
|
id: toolCall.id,
|
|
1003
1059
|
name: toolCall.function.name,
|
|
1004
|
-
input:
|
|
1060
|
+
input: parseToolCallArguments(toolCall.function.arguments)
|
|
1005
1061
|
})) ?? [];
|
|
1062
|
+
return standardToolCalls.length > 0 ? standardToolCalls : parseDsmlToolCalls(message?.content);
|
|
1063
|
+
}
|
|
1064
|
+
function isDsmlToolCallContent(content) {
|
|
1065
|
+
return parseDsmlToolCalls(content).length > 0;
|
|
1006
1066
|
}
|
|
1007
1067
|
var OpenAICompatibleChatModel = class {
|
|
1008
1068
|
constructor(options) {
|
|
@@ -1061,9 +1121,10 @@ var OpenAICompatibleChatModel = class {
|
|
|
1061
1121
|
}
|
|
1062
1122
|
const data2 = await response.json();
|
|
1063
1123
|
const message = data2.choices?.[0]?.message;
|
|
1124
|
+
const toolCalls = parseToolCalls(message);
|
|
1064
1125
|
return {
|
|
1065
|
-
content: message?.content ?? "",
|
|
1066
|
-
toolCalls
|
|
1126
|
+
content: toolCalls.length > 0 && isDsmlToolCallContent(message?.content) ? "" : message?.content ?? "",
|
|
1127
|
+
toolCalls,
|
|
1067
1128
|
reasoningContent: message?.reasoning_content ?? void 0
|
|
1068
1129
|
};
|
|
1069
1130
|
}
|
|
@@ -1081,6 +1142,13 @@ var OpenAICompatibleEmbeddingModel = class {
|
|
|
1081
1142
|
if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {
|
|
1082
1143
|
throw new Error("Embedding \u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
|
|
1083
1144
|
}
|
|
1145
|
+
const vectors = [];
|
|
1146
|
+
for (let index2 = 0; index2 < texts.length; index2 += OPENAI_EMBEDDING_BATCH_SIZE) {
|
|
1147
|
+
vectors.push(...await this.fetchEmbeddingBatch(texts.slice(index2, index2 + OPENAI_EMBEDDING_BATCH_SIZE)));
|
|
1148
|
+
}
|
|
1149
|
+
return vectors;
|
|
1150
|
+
}
|
|
1151
|
+
async fetchEmbeddingBatch(texts) {
|
|
1084
1152
|
const response = await fetch(`${normalizeBaseUrl(this.options.baseUrl)}/embeddings`, {
|
|
1085
1153
|
method: "POST",
|
|
1086
1154
|
headers: {
|
|
@@ -1304,6 +1372,9 @@ var MessageRepository = class {
|
|
|
1304
1372
|
throw new Error("\u539F\u59CB\u56FE\u7247\u6D88\u606F\u4E0D\u5B58\u5728\u3002");
|
|
1305
1373
|
}
|
|
1306
1374
|
const derivedPlatformMessageId = `${source.platformMessageId}:image-summary:${input2.imageKey}`;
|
|
1375
|
+
const imageFileName = input2.imageFileName?.trim();
|
|
1376
|
+
const summaryText = imageFileName ? `[\u56FE\u7247\u8F6C\u8FF0] \u6587\u4EF6\u540D\uFF1A${imageFileName}
|
|
1377
|
+
${input2.summary.trim()}` : `[\u56FE\u7247\u8F6C\u8FF0] ${input2.summary.trim()}`;
|
|
1307
1378
|
return this.ingest({
|
|
1308
1379
|
platform: source.platform,
|
|
1309
1380
|
platformChatId: source.platformChatId,
|
|
@@ -1312,12 +1383,13 @@ var MessageRepository = class {
|
|
|
1312
1383
|
senderId: source.senderId,
|
|
1313
1384
|
senderName: source.senderName,
|
|
1314
1385
|
messageType: "image_summary",
|
|
1315
|
-
text:
|
|
1386
|
+
text: summaryText,
|
|
1316
1387
|
sentAt: source.sentAt,
|
|
1317
1388
|
rawPayload: {
|
|
1318
1389
|
derivedFromMessageId: input2.sourceMessageId,
|
|
1319
1390
|
sourceAttachmentKind: "image",
|
|
1320
1391
|
sourceResourceKey: input2.imageKey,
|
|
1392
|
+
...imageFileName ? { imageFileName } : {},
|
|
1321
1393
|
multimodalModel: input2.multimodalModel,
|
|
1322
1394
|
isMeaningful: true,
|
|
1323
1395
|
...input2.reason?.trim() ? { reason: input2.reason.trim() } : {},
|
|
@@ -2618,7 +2690,7 @@ async function summarizeEpisodeWindow(window, model, now) {
|
|
|
2618
2690
|
const summary = await model.complete([
|
|
2619
2691
|
{
|
|
2620
2692
|
role: "system",
|
|
2621
|
-
content: "\u4F60\u662F ChatterCatcher \u7684\u4F1A\u8BDD\u8BB0\u5FC6\u6574\u7406\u6A21\u5757\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u628A\u788E\u7247\u5316\u95F2\u804A\u6574\u7406\u6210\u53EF\u68C0\u7D22\u4E8B\u5B9E\uFF0C\u8865\u5168\u77ED\u6D88\u606F\u3001\u4EE3\u8BCD\u3001\u7F29\u5199\u4E0E\u4E0A\u4E0B\u6587\u4E4B\u95F4\u7684\u5173\u7CFB\u3002\u53EA\u603B\u7ED3\u660E\u786E\u4E8B\u5B9E\uFF0C\u4E0D\u8981\u7F16\u9020\u3002\u4FDD\u7559\u91CD\u8981\u6570\u5B57\u3001\u65E5\u671F\u3001\u94FE\u63A5\u548C\u4EE3\u7801\uFF1B\u5982\u679C\u5185\u5BB9\u50CF\u5BC6\u7801\u3001API key\u3001token \u6216\u5BC6\u94A5\uFF0C\u53EA\u63CF\u8FF0\u5176\u4E0A\u4E0B\u6587\u5173\u7CFB\uFF0C\u4E0D\u8981\u5728\u6458\u8981\u4E2D\u590D\u5199\u539F\u6587\u3002\u6D88\u606F\u91CC\u7684\u201C\u4ECA\u5929\u201D\u201C\u660E\u5929\u201D\u201C\u6628\u665A\u201D\u201C\u4E0B\u5468\u4E09\u201D\u7B49\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF0C\u8BF7\u57FA\u4E8E\u6BCF\u6761\u6D88\u606F\u524D\u7684\u53D1\u9001\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\u5199\u5165\u6458\u8981\u3002\u4F8B\u5982 [2026-05-05T20:00:00.000Z] \u5988\u5988\u8BF4\u201C\u660E\u5929\u8981\u7528\u4E1D\u4E1D\u9732\u201D\uFF0C\u6458\u8981\u5E94\u5199\u4E3A\u201C2026-05-06 \u8981\u7528\u4E1D\u4E1D\u9732\u201D\u3002"
|
|
2693
|
+
content: "\u4F60\u662F ChatterCatcher \u7684\u4F1A\u8BDD\u8BB0\u5FC6\u6574\u7406\u6A21\u5757\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u628A\u788E\u7247\u5316\u95F2\u804A\u6574\u7406\u6210\u53EF\u68C0\u7D22\u4E8B\u5B9E\uFF0C\u8865\u5168\u77ED\u6D88\u606F\u3001\u4EE3\u8BCD\u3001\u7F29\u5199\u4E0E\u4E0A\u4E0B\u6587\u4E4B\u95F4\u7684\u5173\u7CFB\u3002\u53EA\u603B\u7ED3\u660E\u786E\u4E8B\u5B9E\uFF0C\u4E0D\u8981\u7F16\u9020\u3002\u4FDD\u7559\u91CD\u8981\u6570\u5B57\u3001\u65E5\u671F\u3001\u94FE\u63A5\u3001\u6587\u4EF6\u540D\u548C\u4EE3\u7801\uFF1B\u5982\u679C\u56FE\u7247\u8F6C\u8FF0\u91CC\u51FA\u73B0\u6587\u4EF6\u540D\uFF0C\u5FC5\u987B\u5728\u6458\u8981\u4E2D\u539F\u6837\u4FDD\u7559\u8BE5\u6587\u4EF6\u540D\uFF0C\u65B9\u4FBF\u4E4B\u540E\u6309\u6587\u4EF6\u540D\u627E\u56DE\u56FE\u7247\u3002\u5982\u679C\u5185\u5BB9\u50CF\u5BC6\u7801\u3001API key\u3001token \u6216\u5BC6\u94A5\uFF0C\u53EA\u63CF\u8FF0\u5176\u4E0A\u4E0B\u6587\u5173\u7CFB\uFF0C\u4E0D\u8981\u5728\u6458\u8981\u4E2D\u590D\u5199\u539F\u6587\u3002\u6D88\u606F\u91CC\u7684\u201C\u4ECA\u5929\u201D\u201C\u660E\u5929\u201D\u201C\u6628\u665A\u201D\u201C\u4E0B\u5468\u4E09\u201D\u7B49\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF0C\u8BF7\u57FA\u4E8E\u6BCF\u6761\u6D88\u606F\u524D\u7684\u53D1\u9001\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\u5199\u5165\u6458\u8981\u3002\u4F8B\u5982 [2026-05-05T20:00:00.000Z] \u5988\u5988\u8BF4\u201C\u660E\u5929\u8981\u7528\u4E1D\u4E1D\u9732\u201D\uFF0C\u6458\u8981\u5E94\u5199\u4E3A\u201C2026-05-06 \u8981\u7528\u4E1D\u4E1D\u9732\u201D\u3002"
|
|
2622
2694
|
},
|
|
2623
2695
|
{
|
|
2624
2696
|
role: "user",
|
|
@@ -2723,6 +2795,7 @@ async function ensureFeishuBotOpenId(config, secrets, options = {}) {
|
|
|
2723
2795
|
|
|
2724
2796
|
// src/feishu/gateway.ts
|
|
2725
2797
|
import * as lark2 from "@larksuiteoapi/node-sdk";
|
|
2798
|
+
import path12 from "path";
|
|
2726
2799
|
|
|
2727
2800
|
// src/cron/jobs.ts
|
|
2728
2801
|
import crypto4 from "crypto";
|
|
@@ -2827,6 +2900,7 @@ var CronJobRepository = class {
|
|
|
2827
2900
|
create(input2) {
|
|
2828
2901
|
const schedule = input2.schedule.trim();
|
|
2829
2902
|
const prompt = input2.prompt.trim();
|
|
2903
|
+
const imageFileName = input2.imageFileName?.trim();
|
|
2830
2904
|
if (!isValidCronSchedule(schedule)) {
|
|
2831
2905
|
throw new Error("cron \u8868\u8FBE\u5F0F\u65E0\u6548\u3002");
|
|
2832
2906
|
}
|
|
@@ -2844,6 +2918,7 @@ var CronJobRepository = class {
|
|
|
2844
2918
|
createdByOpenId: input2.createdByOpenId,
|
|
2845
2919
|
schedule,
|
|
2846
2920
|
prompt,
|
|
2921
|
+
...imageFileName ? { imageFileName } : {},
|
|
2847
2922
|
status: "active",
|
|
2848
2923
|
nextRunAt: nextRunAt.toISOString(),
|
|
2849
2924
|
createdAt: now.toISOString(),
|
|
@@ -2852,15 +2927,18 @@ var CronJobRepository = class {
|
|
|
2852
2927
|
this.database.prepare(
|
|
2853
2928
|
`
|
|
2854
2929
|
INSERT INTO cron_jobs (
|
|
2855
|
-
id, chat_id, created_by_open_id, schedule, prompt, status,
|
|
2930
|
+
id, chat_id, created_by_open_id, schedule, prompt, image_file_name, status,
|
|
2856
2931
|
last_run_at, next_run_at, last_error, created_at, updated_at
|
|
2857
2932
|
)
|
|
2858
2933
|
VALUES (
|
|
2859
|
-
@id, @chatId, @createdByOpenId, @schedule, @prompt, @status,
|
|
2934
|
+
@id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName, @status,
|
|
2860
2935
|
NULL, @nextRunAt, NULL, @createdAt, @updatedAt
|
|
2861
2936
|
)
|
|
2862
2937
|
`
|
|
2863
|
-
).run(
|
|
2938
|
+
).run({
|
|
2939
|
+
...record,
|
|
2940
|
+
imageFileName: record.imageFileName ?? null
|
|
2941
|
+
});
|
|
2864
2942
|
return record;
|
|
2865
2943
|
}
|
|
2866
2944
|
get(id) {
|
|
@@ -2885,6 +2963,7 @@ var CronJobRepository = class {
|
|
|
2885
2963
|
created_by_open_id AS createdByOpenId,
|
|
2886
2964
|
schedule,
|
|
2887
2965
|
prompt,
|
|
2966
|
+
image_file_name AS imageFileName,
|
|
2888
2967
|
status,
|
|
2889
2968
|
last_run_at AS lastRunAt,
|
|
2890
2969
|
next_run_at AS nextRunAt,
|
|
@@ -2903,6 +2982,7 @@ var CronJobRepository = class {
|
|
|
2903
2982
|
createdByOpenId: row.createdByOpenId ?? void 0,
|
|
2904
2983
|
schedule: row.schedule,
|
|
2905
2984
|
prompt: row.prompt,
|
|
2985
|
+
imageFileName: row.imageFileName ?? void 0,
|
|
2906
2986
|
status: row.status,
|
|
2907
2987
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
2908
2988
|
nextRunAt: row.nextRunAt,
|
|
@@ -2976,6 +3056,7 @@ var CronJobRepository = class {
|
|
|
2976
3056
|
created_by_open_id AS createdByOpenId,
|
|
2977
3057
|
schedule,
|
|
2978
3058
|
prompt,
|
|
3059
|
+
image_file_name AS imageFileName,
|
|
2979
3060
|
status,
|
|
2980
3061
|
last_run_at AS lastRunAt,
|
|
2981
3062
|
next_run_at AS nextRunAt,
|
|
@@ -2994,6 +3075,7 @@ var CronJobRepository = class {
|
|
|
2994
3075
|
createdByOpenId: row.createdByOpenId ?? void 0,
|
|
2995
3076
|
schedule: row.schedule,
|
|
2996
3077
|
prompt: row.prompt,
|
|
3078
|
+
imageFileName: row.imageFileName ?? void 0,
|
|
2997
3079
|
status: row.status,
|
|
2998
3080
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
2999
3081
|
nextRunAt: row.nextRunAt,
|
|
@@ -3098,6 +3180,12 @@ function createCronJobScheduler(options) {
|
|
|
3098
3180
|
try {
|
|
3099
3181
|
const text = await options.generateMessage(job, startedAt);
|
|
3100
3182
|
await options.sendTextToChat(job.chatId, text);
|
|
3183
|
+
if (job.imageFileName) {
|
|
3184
|
+
if (!options.sendImageToChat) {
|
|
3185
|
+
throw new Error("\u5F53\u524D\u5B9A\u65F6\u4EFB\u52A1\u8FD0\u884C\u73AF\u5883\u4E0D\u652F\u6301\u53D1\u9001\u56FE\u7247\u3002");
|
|
3186
|
+
}
|
|
3187
|
+
await options.sendImageToChat(job.chatId, job.imageFileName);
|
|
3188
|
+
}
|
|
3101
3189
|
options.repository.markSuccess(job.id, startedAt);
|
|
3102
3190
|
} catch (error) {
|
|
3103
3191
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -3238,12 +3326,20 @@ function parseExactNumber2(field, min, max) {
|
|
|
3238
3326
|
}
|
|
3239
3327
|
|
|
3240
3328
|
// src/rag/indexer.ts
|
|
3329
|
+
var EMBEDDING_INDEX_BATCH_SIZE = 64;
|
|
3241
3330
|
async function indexMessageChunks(input2) {
|
|
3242
3331
|
const chunks = input2.messageIds ? input2.messages.listMessageChunksByMessageIds(input2.messageIds, input2.limit ?? 1e4) : input2.messages.listAllMessageChunks(input2.limit ?? 1e4);
|
|
3243
3332
|
if (chunks.length === 0) {
|
|
3244
3333
|
return { chunks: 0, vectors: 0 };
|
|
3245
3334
|
}
|
|
3246
|
-
const vectors =
|
|
3335
|
+
const vectors = [];
|
|
3336
|
+
for (let index2 = 0; index2 < chunks.length; index2 += EMBEDDING_INDEX_BATCH_SIZE) {
|
|
3337
|
+
vectors.push(
|
|
3338
|
+
...await input2.embedding.embedBatch(
|
|
3339
|
+
chunks.slice(index2, index2 + EMBEDDING_INDEX_BATCH_SIZE).map((chunk) => chunk.text)
|
|
3340
|
+
)
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3247
3343
|
const records = [];
|
|
3248
3344
|
for (const [index2, chunk] of chunks.entries()) {
|
|
3249
3345
|
const vector = vectors[index2];
|
|
@@ -3514,6 +3610,7 @@ var ImageMultimodalTaskRepository = class {
|
|
|
3514
3610
|
};
|
|
3515
3611
|
|
|
3516
3612
|
// src/multimodal/worker.ts
|
|
3613
|
+
import path11 from "path";
|
|
3517
3614
|
var ImageMultimodalWorker = class {
|
|
3518
3615
|
constructor(options) {
|
|
3519
3616
|
this.options = options;
|
|
@@ -3540,9 +3637,11 @@ var ImageMultimodalWorker = class {
|
|
|
3540
3637
|
throw error;
|
|
3541
3638
|
}
|
|
3542
3639
|
try {
|
|
3640
|
+
const imageFileName = path11.basename(running.storedPath);
|
|
3543
3641
|
const described = await this.options.model.describeImage({
|
|
3544
3642
|
imagePath: running.storedPath,
|
|
3545
|
-
mimeType: running.mimeType
|
|
3643
|
+
mimeType: running.mimeType,
|
|
3644
|
+
context: `\u56FE\u7247\u6587\u4EF6\u540D\uFF1A${imageFileName}`
|
|
3546
3645
|
});
|
|
3547
3646
|
if (!described.isMeaningful) {
|
|
3548
3647
|
this.options.tasks.markSkipped(running.id, described.reason || "\u591A\u6A21\u6001\u6A21\u578B\u5224\u5B9A\u56FE\u7247\u65E0\u610F\u4E49\u3002");
|
|
@@ -3552,6 +3651,7 @@ var ImageMultimodalWorker = class {
|
|
|
3552
3651
|
const derivedMessageId = this.options.messages.createImageSummaryMessage({
|
|
3553
3652
|
sourceMessageId: running.sourceMessageId,
|
|
3554
3653
|
imageKey: running.imageKey,
|
|
3654
|
+
imageFileName,
|
|
3555
3655
|
summary: described.summary,
|
|
3556
3656
|
reason: described.reason,
|
|
3557
3657
|
multimodalModel: this.options.multimodalModelName,
|
|
@@ -3585,6 +3685,17 @@ function readString(input2, key) {
|
|
|
3585
3685
|
}
|
|
3586
3686
|
return value.trim();
|
|
3587
3687
|
}
|
|
3688
|
+
function readOptionalString(input2, key) {
|
|
3689
|
+
const value = typeof input2 === "object" && input2 !== null && key in input2 ? input2[key] : void 0;
|
|
3690
|
+
if (value === void 0 || value === null) {
|
|
3691
|
+
return void 0;
|
|
3692
|
+
}
|
|
3693
|
+
if (typeof value !== "string") {
|
|
3694
|
+
throw new Error(`${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002`);
|
|
3695
|
+
}
|
|
3696
|
+
const trimmed = value.trim();
|
|
3697
|
+
return trimmed || void 0;
|
|
3698
|
+
}
|
|
3588
3699
|
function createCronJobTools(input2) {
|
|
3589
3700
|
return [
|
|
3590
3701
|
{
|
|
@@ -3600,6 +3711,10 @@ function createCronJobTools(input2) {
|
|
|
3600
3711
|
prompt: {
|
|
3601
3712
|
type: "string",
|
|
3602
3713
|
description: "Prompt used later to generate the scheduled message."
|
|
3714
|
+
},
|
|
3715
|
+
imageFileName: {
|
|
3716
|
+
type: "string",
|
|
3717
|
+
description: "Optional image filename already stored from the current chat, for example om_xxx-image.jpg."
|
|
3603
3718
|
}
|
|
3604
3719
|
},
|
|
3605
3720
|
required: ["schedule", "prompt"],
|
|
@@ -3610,7 +3725,8 @@ function createCronJobTools(input2) {
|
|
|
3610
3725
|
chatId: input2.chatId,
|
|
3611
3726
|
createdByOpenId: input2.createdByOpenId,
|
|
3612
3727
|
schedule: readString(rawInput, "schedule"),
|
|
3613
|
-
prompt: readString(rawInput, "prompt")
|
|
3728
|
+
prompt: readString(rawInput, "prompt"),
|
|
3729
|
+
imageFileName: readOptionalString(rawInput, "imageFileName")
|
|
3614
3730
|
});
|
|
3615
3731
|
return JSON.stringify({ ok: true, job });
|
|
3616
3732
|
}
|
|
@@ -3773,11 +3889,14 @@ function stripMentions(text, mentions) {
|
|
|
3773
3889
|
}
|
|
3774
3890
|
return result.replace(/@/g, " ").replace(/\s+/g, " ").trim();
|
|
3775
3891
|
}
|
|
3776
|
-
var FEISHU_TOOL_SYSTEM_PROMPT = `\u4F60\u662F\u98DE\u4E66\u7FA4\u804A\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u5148\u641C\u7D22\u672C\u5730\u77E5\u8BC6\u6765\u56DE\u7B54\u95EE\u9898\uFF1B\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u521B\u5EFA\u3001\u67E5\u770B\u6216\u5220\u9664\u7FA4\u6D88\u606F\u5B9A\u65F6\u4EFB\u52A1\u65F6\uFF0C\u4E5F\u53EF\u4EE5\u8C03\u7528\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u3002\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u53EA\u7BA1\u7406\u5F53\u524D\u7FA4\u804A\uFF0C\u4E0D\u80FD\u8DE8\u7FA4\u64CD\u4F5C\u3002\u82E5\u7528\u6237\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u65F6\u95F4\uFF0C\u4F60\u9700\u8981\u5148\u5C06\u5176\u8F6C\u6362\u4E3A\u4E94\u5B57\u6BB5 cron \u8868\u8FBE\u5F0F\uFF08\u5206 \u65F6 \u65E5 \u6708 \u5468\uFF09\uFF0C\u518D\u8C03\u7528\u5DE5\u5177\u3002\u5F53\u524D\u65F6\u95F4\u4F1A\u63D0\u4F9B\u7ED9\u4F60\u3002\u68C0\u7D22\u8BC1\u636E\u4E2D\u7684\u65F6\u95F4\u6233\u662F\u6D88\u606F\u88AB\u53D1\u9001\u65F6\u7684\u771F\u5B9E\u65F6\u95F4\u3002\u56DE\u7B54\u65F6\u82E5\u6D89\u53CA\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF08\u5982\u6D88\u606F\u4E2D\u8BF4\u201D\u660E\u5929\u201D\u201D\u4ECA\u665A\u201D\uFF09\uFF0C\u5FC5\u987B\u57FA\u4E8E\u8BC1\u636E\u4E2D\u6BCF\u6761\u6D88\u606F\u7684\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\uFF0C\u4E0D\u8981\u7167\u642C\u539F\u6587\u7684\u76F8\u5BF9\u8868\u8FF0\u3002\u5BF9\u4E8E\u4E00\u822C\u95EE\u7B54\uFF0C\u5148\u6309\u9700\u8C03\u7528\u641C\u7D22\u5DE5\u5177\uFF0C\u518D\u57FA\u4E8E\u5DE5\u5177\u8FD4\u56DE\u7684\u8BC1\u636E\u76F4\u63A5\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\uFF1B\u82E5\u5F15\u7528\u4E86\u68C0\u7D22\u7ED3\u679C\uFF0C\u8981\u5728\u7B54\u6848\u91CC\u76F4\u63A5\u5199\u51FA\u5F15\u7528\u5185\u5BB9\u3002\u4E0D\u8981\u58F0\u79F0\u5B8C\u6210\u4E86\u672A\u5B9E\u9645\u8C03\u7528\u7684\u64CD\u4F5C\u3002\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u7B54\u5FC5\u987B\u662F\u9762\u5411\u7FA4\u6210\u5458\u7684\u81EA\u7136\u8BED\u8A00\uFF0C\u7EDD\u5BF9\u4E0D\u80FD\u8F93\u51FA JSON\u3001\u5DE5\u5177\u8C03\u7528\u7EC6\u8282\u6216\u539F\u59CB\u7684\u641C\u7D22\u7ED3\u679C\u683C\u5F0F\u3002\u7528\u6237\u53EA\u5E94\u770B\u5230\u4F60\u6574\u5408\u540E\u7684\u6700\u7EC8\u7B54\u6848\u3002`;
|
|
3892
|
+
var FEISHU_TOOL_SYSTEM_PROMPT = `\u4F60\u662F\u98DE\u4E66\u7FA4\u804A\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u5148\u641C\u7D22\u672C\u5730\u77E5\u8BC6\u6765\u56DE\u7B54\u95EE\u9898\uFF1B\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u521B\u5EFA\u3001\u67E5\u770B\u6216\u5220\u9664\u7FA4\u6D88\u606F\u5B9A\u65F6\u4EFB\u52A1\u65F6\uFF0C\u4E5F\u53EF\u4EE5\u8C03\u7528\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u3002\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u53EA\u7BA1\u7406\u5F53\u524D\u7FA4\u804A\uFF0C\u4E0D\u80FD\u8DE8\u7FA4\u64CD\u4F5C\u3002\u82E5\u7528\u6237\u8981\u6C42\u5B9A\u65F6\u4EFB\u52A1\u53D1\u9001\u56FE\u7247\uFF0C\u53EA\u80FD\u4F7F\u7528\u5F53\u524D\u7FA4\u804A\u91CC\u5DF2\u7ECF\u4E0B\u8F7D\u5165\u5E93\u7684\u56FE\u7247\u6587\u4EF6\u540D\uFF0C\u5E76\u5728\u521B\u5EFA\u5B9A\u65F6\u4EFB\u52A1\u65F6\u628A\u6587\u4EF6\u540D\u586B\u5165 imageFileName\uFF1B\u4E0D\u8981\u7F16\u9020\u672C\u5730\u8DEF\u5F84\u3002\u82E5\u7528\u6237\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u65F6\u95F4\uFF0C\u4F60\u9700\u8981\u5148\u5C06\u5176\u8F6C\u6362\u4E3A\u4E94\u5B57\u6BB5 cron \u8868\u8FBE\u5F0F\uFF08\u5206 \u65F6 \u65E5 \u6708 \u5468\uFF09\uFF0C\u518D\u8C03\u7528\u5DE5\u5177\u3002\u5F53\u524D\u65F6\u95F4\u4F1A\u63D0\u4F9B\u7ED9\u4F60\u3002\u68C0\u7D22\u8BC1\u636E\u4E2D\u7684\u65F6\u95F4\u6233\u662F\u6D88\u606F\u88AB\u53D1\u9001\u65F6\u7684\u771F\u5B9E\u65F6\u95F4\u3002\u56DE\u7B54\u65F6\u82E5\u6D89\u53CA\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF08\u5982\u6D88\u606F\u4E2D\u8BF4\u201D\u660E\u5929\u201D\u201D\u4ECA\u665A\u201D\uFF09\uFF0C\u5FC5\u987B\u57FA\u4E8E\u8BC1\u636E\u4E2D\u6BCF\u6761\u6D88\u606F\u7684\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\uFF0C\u4E0D\u8981\u7167\u642C\u539F\u6587\u7684\u76F8\u5BF9\u8868\u8FF0\u3002\u5BF9\u4E8E\u4E00\u822C\u95EE\u7B54\uFF0C\u5148\u6309\u9700\u8C03\u7528\u641C\u7D22\u5DE5\u5177\uFF0C\u518D\u57FA\u4E8E\u5DE5\u5177\u8FD4\u56DE\u7684\u8BC1\u636E\u76F4\u63A5\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\uFF1B\u82E5\u5F15\u7528\u4E86\u68C0\u7D22\u7ED3\u679C\uFF0C\u8981\u5728\u7B54\u6848\u91CC\u76F4\u63A5\u5199\u51FA\u5F15\u7528\u5185\u5BB9\u3002\u4E0D\u8981\u58F0\u79F0\u5B8C\u6210\u4E86\u672A\u5B9E\u9645\u8C03\u7528\u7684\u64CD\u4F5C\u3002\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u7B54\u5FC5\u987B\u662F\u9762\u5411\u7FA4\u6210\u5458\u7684\u81EA\u7136\u8BED\u8A00\uFF0C\u7EDD\u5BF9\u4E0D\u80FD\u8F93\u51FA JSON\u3001\u5DE5\u5177\u8C03\u7528\u7EC6\u8282\u6216\u539F\u59CB\u7684\u641C\u7D22\u7ED3\u679C\u683C\u5F0F\u3002\u7528\u6237\u53EA\u5E94\u770B\u5230\u4F60\u6574\u5408\u540E\u7684\u6700\u7EC8\u7B54\u6848\u3002`;
|
|
3777
3893
|
var DEFAULT_MAX_MODEL_TURNS = 4;
|
|
3778
3894
|
var DEFAULT_MAX_TOOL_CALLS = 8;
|
|
3779
3895
|
var FEISHU_TOOL_LOOP_FALLBACK = "\u5B9A\u65F6\u4EFB\u52A1\u64CD\u4F5C\u5DF2\u63D0\u4EA4\uFF0C\u4F46\u6A21\u578B\u6CA1\u6709\u751F\u6210\u6700\u7EC8\u56DE\u590D\u3002";
|
|
3780
3896
|
var FEISHU_TOOL_LOOP_LIMIT_REACHED = "\u5DE5\u5177\u8C03\u7528\u6B21\u6570\u5DF2\u8FBE\u5230\u4E0A\u9650\uFF0C\u8BF7\u7F29\u5C0F\u8BF7\u6C42\u540E\u91CD\u8BD5\u3002";
|
|
3897
|
+
function containsRawToolCallMarkup(content) {
|
|
3898
|
+
return /<||DSML||tool_calls>|<||DSML||invoke\s+name=|<tool_call>|<tool_calls>/i.test(content);
|
|
3899
|
+
}
|
|
3781
3900
|
function toToolResultContent(value) {
|
|
3782
3901
|
if (typeof value === "string") return value;
|
|
3783
3902
|
return JSON.stringify(value);
|
|
@@ -3820,6 +3939,7 @@ async function runFeishuToolLoop(input2) {
|
|
|
3820
3939
|
let toolCallsUsed = 0;
|
|
3821
3940
|
for (let turn = 0; turn < maxModelTurns; turn += 1) {
|
|
3822
3941
|
const assistantResult = await input2.model.completeWithTools(messages, input2.tools);
|
|
3942
|
+
const hasRawToolCallMarkup = containsRawToolCallMarkup(assistantResult.content);
|
|
3823
3943
|
messages.push({
|
|
3824
3944
|
role: "assistant",
|
|
3825
3945
|
content: assistantResult.content,
|
|
@@ -3827,6 +3947,9 @@ async function runFeishuToolLoop(input2) {
|
|
|
3827
3947
|
reasoningContent: assistantResult.reasoningContent
|
|
3828
3948
|
});
|
|
3829
3949
|
if (assistantResult.toolCalls.length === 0) {
|
|
3950
|
+
if (hasRawToolCallMarkup) {
|
|
3951
|
+
break;
|
|
3952
|
+
}
|
|
3830
3953
|
return assistantResult.content || FEISHU_TOOL_LOOP_FALLBACK;
|
|
3831
3954
|
}
|
|
3832
3955
|
for (const toolCall of assistantResult.toolCalls) {
|
|
@@ -3939,7 +4062,7 @@ var FeishuQuestionHandler = class {
|
|
|
3939
4062
|
}
|
|
3940
4063
|
if (this.options.sender.addReactionToMessage) {
|
|
3941
4064
|
try {
|
|
3942
|
-
await this.options.sender.addReactionToMessage(messageId, this.options.thinkingEmojiType ?? "
|
|
4065
|
+
await this.options.sender.addReactionToMessage(messageId, this.options.thinkingEmojiType ?? "OK");
|
|
3943
4066
|
return;
|
|
3944
4067
|
} catch (error) {
|
|
3945
4068
|
console.log(`\u98DE\u4E66\u63D0\u95EE\u8868\u60C5\u53CD\u9988\u5931\u8D25\uFF0C\u6539\u7528\u6587\u5B57\u53CD\u9988\uFF1A${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -4012,9 +4135,22 @@ var FeishuQuestionHandler = class {
|
|
|
4012
4135
|
|
|
4013
4136
|
// src/feishu/sender.ts
|
|
4014
4137
|
import * as lark from "@larksuiteoapi/node-sdk";
|
|
4138
|
+
import fs9 from "fs/promises";
|
|
4015
4139
|
function mapDomain(domain) {
|
|
4016
4140
|
return domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
|
|
4017
4141
|
}
|
|
4142
|
+
function extractImageKey(response) {
|
|
4143
|
+
const data2 = response && typeof response === "object" ? response : {};
|
|
4144
|
+
const direct = data2.image_key;
|
|
4145
|
+
if (typeof direct === "string" && direct.trim()) {
|
|
4146
|
+
return direct.trim();
|
|
4147
|
+
}
|
|
4148
|
+
const nested = data2.data && typeof data2.data === "object" ? data2.data.image_key : void 0;
|
|
4149
|
+
if (typeof nested === "string" && nested.trim()) {
|
|
4150
|
+
return nested.trim();
|
|
4151
|
+
}
|
|
4152
|
+
throw new Error("\u98DE\u4E66\u56FE\u7247\u4E0A\u4F20\u54CD\u5E94\u7F3A\u5C11 image_key\u3002");
|
|
4153
|
+
}
|
|
4018
4154
|
var FeishuMessageSender = class _FeishuMessageSender {
|
|
4019
4155
|
constructor(client) {
|
|
4020
4156
|
this.client = client;
|
|
@@ -4051,6 +4187,39 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4051
4187
|
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4052
4188
|
}
|
|
4053
4189
|
}
|
|
4190
|
+
async sendImageToChat(chatId, imagePath) {
|
|
4191
|
+
const imageCreate = this.client.im.v1?.image?.create;
|
|
4192
|
+
if (!imageCreate) {
|
|
4193
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u56FE\u7247\u4E0A\u4F20\u63A5\u53E3\u3002");
|
|
4194
|
+
}
|
|
4195
|
+
const image = await fs9.readFile(imagePath);
|
|
4196
|
+
const uploaded = await imageCreate({
|
|
4197
|
+
data: {
|
|
4198
|
+
image_type: "message",
|
|
4199
|
+
image
|
|
4200
|
+
}
|
|
4201
|
+
});
|
|
4202
|
+
const imageKey = extractImageKey(uploaded);
|
|
4203
|
+
const payload = {
|
|
4204
|
+
data: {
|
|
4205
|
+
receive_id: chatId,
|
|
4206
|
+
msg_type: "image",
|
|
4207
|
+
content: JSON.stringify({ image_key: imageKey })
|
|
4208
|
+
},
|
|
4209
|
+
params: {
|
|
4210
|
+
receive_id_type: "chat_id"
|
|
4211
|
+
}
|
|
4212
|
+
};
|
|
4213
|
+
if (this.client.im.v1?.message.create) {
|
|
4214
|
+
await this.client.im.v1.message.create(payload);
|
|
4215
|
+
return;
|
|
4216
|
+
}
|
|
4217
|
+
if (this.client.im.message?.create) {
|
|
4218
|
+
await this.client.im.message.create(payload);
|
|
4219
|
+
return;
|
|
4220
|
+
}
|
|
4221
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4222
|
+
}
|
|
4054
4223
|
async replyTextToMessage(messageId, text) {
|
|
4055
4224
|
const payload = {
|
|
4056
4225
|
path: {
|
|
@@ -4191,6 +4360,13 @@ function createFeishuEventDispatcher(options) {
|
|
|
4191
4360
|
}
|
|
4192
4361
|
});
|
|
4193
4362
|
}
|
|
4363
|
+
function resolveFeishuImagePath(config, imageFileName) {
|
|
4364
|
+
const fileName = path12.basename(imageFileName.trim());
|
|
4365
|
+
if (!fileName || fileName !== imageFileName.trim()) {
|
|
4366
|
+
throw new Error("\u56FE\u7247\u6587\u4EF6\u540D\u65E0\u6548\u3002");
|
|
4367
|
+
}
|
|
4368
|
+
return path12.join(resolveHomePath(config.storage.dataDir), "files", "feishu", fileName);
|
|
4369
|
+
}
|
|
4194
4370
|
function createFeishuGateway(options) {
|
|
4195
4371
|
assertFeishuConfig(options.config, options.secrets);
|
|
4196
4372
|
const wsClient = options.wsClientFactory?.({
|
|
@@ -4236,6 +4412,10 @@ function createFeishuGateway(options) {
|
|
|
4236
4412
|
const cronJobScheduler = options.cronJobScheduler ?? (options.cronJobProcessor ? createCronJobScheduler({
|
|
4237
4413
|
repository: new CronJobRepository(options.cronJobProcessor.database),
|
|
4238
4414
|
sendTextToChat: (chatId, text) => options.cronJobProcessor.sender.sendTextToChat(chatId, text),
|
|
4415
|
+
sendImageToChat: options.cronJobProcessor.sender.sendImageToChat ? (chatId, imageFileName) => options.cronJobProcessor.sender.sendImageToChat(
|
|
4416
|
+
chatId,
|
|
4417
|
+
resolveFeishuImagePath(options.config, imageFileName)
|
|
4418
|
+
) : void 0,
|
|
4239
4419
|
generateMessage: async (job, now) => {
|
|
4240
4420
|
const { tools, close } = await createAgenticRagSearchTools({
|
|
4241
4421
|
config: options.config,
|
|
@@ -4273,8 +4453,8 @@ function createFeishuGateway(options) {
|
|
|
4273
4453
|
|
|
4274
4454
|
// src/feishu/resource-downloader.ts
|
|
4275
4455
|
import * as lark3 from "@larksuiteoapi/node-sdk";
|
|
4276
|
-
import
|
|
4277
|
-
import
|
|
4456
|
+
import fs10 from "fs/promises";
|
|
4457
|
+
import path13 from "path";
|
|
4278
4458
|
var RESOURCE_TYPE_BY_KIND = {
|
|
4279
4459
|
file: "file",
|
|
4280
4460
|
image: "image",
|
|
@@ -4312,10 +4492,10 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
4312
4492
|
}
|
|
4313
4493
|
async download(input2) {
|
|
4314
4494
|
const resourceType = RESOURCE_TYPE_BY_KIND[input2.attachment.kind];
|
|
4315
|
-
const targetDir =
|
|
4316
|
-
await
|
|
4495
|
+
const targetDir = path13.join(this.dataDir, "files", "feishu");
|
|
4496
|
+
await fs10.mkdir(targetDir, { recursive: true });
|
|
4317
4497
|
const fileName = buildStoredFileName(input2);
|
|
4318
|
-
const storedPath =
|
|
4498
|
+
const storedPath = path13.join(targetDir, fileName);
|
|
4319
4499
|
const payload = {
|
|
4320
4500
|
params: { type: resourceType },
|
|
4321
4501
|
path: { message_id: input2.messageId, file_key: input2.attachment.fileKey }
|
|
@@ -4338,29 +4518,29 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
4338
4518
|
|
|
4339
4519
|
// src/files/ingest.ts
|
|
4340
4520
|
import crypto7 from "crypto";
|
|
4341
|
-
import
|
|
4342
|
-
import
|
|
4521
|
+
import fs12 from "fs/promises";
|
|
4522
|
+
import path15 from "path";
|
|
4343
4523
|
|
|
4344
4524
|
// src/files/parser.ts
|
|
4345
|
-
import
|
|
4346
|
-
import
|
|
4525
|
+
import fs11 from "fs/promises";
|
|
4526
|
+
import path14 from "path";
|
|
4347
4527
|
import mammoth from "mammoth";
|
|
4348
4528
|
import { PDFParse } from "pdf-parse";
|
|
4349
4529
|
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".markdown", ".json", ".csv", ".tsv", ".log"]);
|
|
4350
4530
|
var DOCX_EXTENSIONS = /* @__PURE__ */ new Set([".docx"]);
|
|
4351
4531
|
var PDF_EXTENSIONS = /* @__PURE__ */ new Set([".pdf"]);
|
|
4352
4532
|
function isSupportedParseFile(filePath) {
|
|
4353
|
-
const extension =
|
|
4533
|
+
const extension = path14.extname(filePath).toLowerCase();
|
|
4354
4534
|
return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);
|
|
4355
4535
|
}
|
|
4356
4536
|
function describeSupportedParseTypes() {
|
|
4357
4537
|
return "txt\u3001md\u3001json\u3001csv\u3001tsv\u3001log\u3001docx\u3001pdf";
|
|
4358
4538
|
}
|
|
4359
4539
|
async function parseFileToText(filePath) {
|
|
4360
|
-
const extension =
|
|
4540
|
+
const extension = path14.extname(filePath).toLowerCase();
|
|
4361
4541
|
if (TEXT_EXTENSIONS.has(extension)) {
|
|
4362
4542
|
return {
|
|
4363
|
-
text: await
|
|
4543
|
+
text: await fs11.readFile(filePath, "utf8"),
|
|
4364
4544
|
parser: "text",
|
|
4365
4545
|
warnings: []
|
|
4366
4546
|
};
|
|
@@ -4374,7 +4554,7 @@ async function parseFileToText(filePath) {
|
|
|
4374
4554
|
};
|
|
4375
4555
|
}
|
|
4376
4556
|
if (PDF_EXTENSIONS.has(extension)) {
|
|
4377
|
-
const buffer = await
|
|
4557
|
+
const buffer = await fs11.readFile(filePath);
|
|
4378
4558
|
const parser = new PDFParse({ data: buffer });
|
|
4379
4559
|
try {
|
|
4380
4560
|
const result = await parser.getText();
|
|
@@ -4396,7 +4576,7 @@ function isSupportedTextFile(filePath) {
|
|
|
4396
4576
|
}
|
|
4397
4577
|
function ensureSupportedTextFile(filePath) {
|
|
4398
4578
|
if (!isSupportedTextFile(filePath)) {
|
|
4399
|
-
const extension =
|
|
4579
|
+
const extension = path15.extname(filePath).toLowerCase();
|
|
4400
4580
|
throw new Error(`\u6682\u4E0D\u652F\u6301\u8BE5\u6587\u4EF6\u7C7B\u578B\uFF1A${extension || "\u65E0\u6269\u5C55\u540D"}\u3002\u5F53\u524D\u652F\u6301 ${describeSupportedParseTypes()}\u3002`);
|
|
4401
4581
|
}
|
|
4402
4582
|
}
|
|
@@ -4405,12 +4585,12 @@ function stableStoredName(sourcePath, fileName) {
|
|
|
4405
4585
|
return `${digest}-${fileName}`;
|
|
4406
4586
|
}
|
|
4407
4587
|
async function ingestLocalFile(input2) {
|
|
4408
|
-
const sourcePath =
|
|
4409
|
-
const fileName =
|
|
4588
|
+
const sourcePath = path15.resolve(input2.filePath);
|
|
4589
|
+
const fileName = path15.basename(sourcePath);
|
|
4410
4590
|
const jobId = input2.jobs?.start({ sourcePath, fileName });
|
|
4411
4591
|
try {
|
|
4412
4592
|
ensureSupportedTextFile(sourcePath);
|
|
4413
|
-
const stat = await
|
|
4593
|
+
const stat = await fs12.stat(sourcePath);
|
|
4414
4594
|
if (!stat.isFile()) {
|
|
4415
4595
|
throw new Error(`\u4E0D\u662F\u6587\u4EF6\uFF1A${sourcePath}`);
|
|
4416
4596
|
}
|
|
@@ -4419,10 +4599,10 @@ async function ingestLocalFile(input2) {
|
|
|
4419
4599
|
if (!text) {
|
|
4420
4600
|
throw new Error(`\u6587\u4EF6\u6CA1\u6709\u53EF\u7D22\u5F15\u6587\u672C\uFF1A${sourcePath}`);
|
|
4421
4601
|
}
|
|
4422
|
-
const fileDir =
|
|
4423
|
-
await
|
|
4424
|
-
const storedPath =
|
|
4425
|
-
await
|
|
4602
|
+
const fileDir = path15.join(resolveHomePath(input2.config.storage.dataDir), "files");
|
|
4603
|
+
await fs12.mkdir(fileDir, { recursive: true });
|
|
4604
|
+
const storedPath = path15.join(fileDir, stableStoredName(sourcePath, fileName));
|
|
4605
|
+
await fs12.copyFile(sourcePath, storedPath);
|
|
4426
4606
|
const messageId = input2.messages.ingest({
|
|
4427
4607
|
platform: "local-file",
|
|
4428
4608
|
platformChatId: "local-files",
|
|
@@ -4727,8 +4907,8 @@ var GatewayIngestor = class {
|
|
|
4727
4907
|
|
|
4728
4908
|
// src/gateway/detached.ts
|
|
4729
4909
|
import { spawn } from "child_process";
|
|
4730
|
-
import
|
|
4731
|
-
import
|
|
4910
|
+
import fs13 from "fs";
|
|
4911
|
+
import path16 from "path";
|
|
4732
4912
|
var START_FAILURE_GRACE_MS = 250;
|
|
4733
4913
|
function buildGatewayForegroundSpawnCommand(argv = process.argv) {
|
|
4734
4914
|
const [command = process.execPath, ...rawArgs] = argv;
|
|
@@ -4785,7 +4965,7 @@ async function startDetachedGateway(input2) {
|
|
|
4785
4965
|
...status.pid ? { pid: status.pid } : {}
|
|
4786
4966
|
};
|
|
4787
4967
|
}
|
|
4788
|
-
|
|
4968
|
+
fs13.mkdirSync(path16.dirname(logFile), { recursive: true });
|
|
4789
4969
|
let out;
|
|
4790
4970
|
let err;
|
|
4791
4971
|
let stdioClosed = false;
|
|
@@ -4795,15 +4975,15 @@ async function startDetachedGateway(input2) {
|
|
|
4795
4975
|
}
|
|
4796
4976
|
stdioClosed = true;
|
|
4797
4977
|
if (typeof out === "number") {
|
|
4798
|
-
|
|
4978
|
+
fs13.closeSync(out);
|
|
4799
4979
|
}
|
|
4800
4980
|
if (typeof err === "number") {
|
|
4801
|
-
|
|
4981
|
+
fs13.closeSync(err);
|
|
4802
4982
|
}
|
|
4803
4983
|
};
|
|
4804
4984
|
try {
|
|
4805
|
-
out =
|
|
4806
|
-
err =
|
|
4985
|
+
out = fs13.openSync(logFile, "a");
|
|
4986
|
+
err = fs13.openSync(logFile, "a");
|
|
4807
4987
|
const foreground = buildGatewayForegroundSpawnCommand(input2.argv);
|
|
4808
4988
|
const child = spawn(foreground.command, foreground.args, {
|
|
4809
4989
|
detached: true,
|
|
@@ -4834,7 +5014,7 @@ async function startDetachedGateway(input2) {
|
|
|
4834
5014
|
}
|
|
4835
5015
|
|
|
4836
5016
|
// src/multimodal/openai-compatible.ts
|
|
4837
|
-
import
|
|
5017
|
+
import fs14 from "fs/promises";
|
|
4838
5018
|
function normalizeBaseUrl2(baseUrl) {
|
|
4839
5019
|
return baseUrl.replace(/\/+$/, "");
|
|
4840
5020
|
}
|
|
@@ -4844,6 +5024,7 @@ function buildPrompt(context) {
|
|
|
4844
5024
|
"\u8BF7\u7406\u89E3\u8FD9\u5F20\u56FE\u7247\uFF0C\u5224\u65AD\u5B83\u662F\u5426\u5305\u542B\u503C\u5F97\u8FDB\u5165\u77E5\u8BC6\u5E93\u548C\u4F1A\u8BDD\u8BB0\u5FC6\u7684\u6709\u610F\u4E49\u4FE1\u606F\u3002",
|
|
4845
5025
|
'\u8BF7\u53EA\u8F93\u51FA JSON\uFF0C\u683C\u5F0F\u4E3A {"summary": string, "isMeaningful": boolean, "reason": string}\u3002',
|
|
4846
5026
|
"summary \u4F7F\u7528\u7B80\u6D01\u4E2D\u6587\u8F6C\u8FF0\u56FE\u7247\u4E2D\u7684\u5173\u952E\u4FE1\u606F\uFF1B\u65E0\u610F\u4E49\u56FE\u7247\u4E5F\u8981\u7ED9\u51FA\u7B80\u77ED summary\u3002",
|
|
5027
|
+
"\u5982\u679C\u4E0A\u4E0B\u6587\u63D0\u4F9B\u4E86\u56FE\u7247\u6587\u4EF6\u540D\uFF0Csummary \u5FC5\u987B\u539F\u6837\u5305\u542B\u8BE5\u6587\u4EF6\u540D\uFF0C\u4FBF\u4E8E\u4E4B\u540E\u6309\u6587\u4EF6\u540D\u68C0\u7D22\u548C\u53D1\u9001\u56FE\u7247\u3002",
|
|
4847
5028
|
contextText ? `\u4E0A\u4E0B\u6587\uFF1A${contextText}` : void 0
|
|
4848
5029
|
].filter(Boolean).join("\n");
|
|
4849
5030
|
}
|
|
@@ -4881,7 +5062,7 @@ var OpenAICompatibleMultimodalModel = class {
|
|
|
4881
5062
|
if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {
|
|
4882
5063
|
throw new Error("\u591A\u6A21\u6001\u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
|
|
4883
5064
|
}
|
|
4884
|
-
const image = await
|
|
5065
|
+
const image = await fs14.readFile(input2.imagePath);
|
|
4885
5066
|
const response = await fetch(`${normalizeBaseUrl2(this.options.baseUrl)}/chat/completions`, {
|
|
4886
5067
|
method: "POST",
|
|
4887
5068
|
headers: {
|
|
@@ -6415,7 +6596,7 @@ dev.command("ingest-feishu-event").description("\u4ECE JSON \u6587\u4EF6\u6A21\u
|
|
|
6415
6596
|
const config = await loadConfig();
|
|
6416
6597
|
const database = openDatabase(config);
|
|
6417
6598
|
try {
|
|
6418
|
-
const raw = await
|
|
6599
|
+
const raw = await fs15.readFile(options.file, "utf8");
|
|
6419
6600
|
const payload = JSON.parse(raw);
|
|
6420
6601
|
const result = new GatewayIngestor(database).ingestFeishuEvent(payload);
|
|
6421
6602
|
if (!result.accepted) {
|