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/index.d.ts
CHANGED
|
@@ -134,6 +134,7 @@ interface IngestMessageInput {
|
|
|
134
134
|
interface CreateImageSummaryMessageInput {
|
|
135
135
|
sourceMessageId: string;
|
|
136
136
|
imageKey: string;
|
|
137
|
+
imageFileName?: string;
|
|
137
138
|
summary: string;
|
|
138
139
|
reason?: string;
|
|
139
140
|
multimodalModel: string;
|
|
@@ -237,6 +238,7 @@ interface CronJobRecord {
|
|
|
237
238
|
createdByOpenId?: string;
|
|
238
239
|
schedule: string;
|
|
239
240
|
prompt: string;
|
|
241
|
+
imageFileName?: string;
|
|
240
242
|
status: CronJobStatus;
|
|
241
243
|
lastRunAt?: string;
|
|
242
244
|
nextRunAt: string;
|
|
@@ -256,6 +258,7 @@ declare class CronJobRepository {
|
|
|
256
258
|
createdByOpenId?: string;
|
|
257
259
|
schedule: string;
|
|
258
260
|
prompt: string;
|
|
261
|
+
imageFileName?: string;
|
|
259
262
|
}): CronJobRecord;
|
|
260
263
|
get(id: string): CronJobRecord | null;
|
|
261
264
|
list(limit?: number): CronJobRecord[];
|
|
@@ -280,6 +283,7 @@ interface CreateCronJobSchedulerOptions {
|
|
|
280
283
|
repository: Pick<CronJobRepository, "listDue" | "markSuccess" | "markFailure">;
|
|
281
284
|
generateMessage: (job: CronJobRecord, now: Date) => Promise<string>;
|
|
282
285
|
sendTextToChat: (chatId: string, text: string) => Promise<void>;
|
|
286
|
+
sendImageToChat?: (chatId: string, imageFileName: string) => Promise<void>;
|
|
283
287
|
now?: () => Date;
|
|
284
288
|
setIntervalFn?: typeof setInterval;
|
|
285
289
|
clearIntervalFn?: typeof clearInterval;
|
|
@@ -594,6 +598,7 @@ interface IndexingScheduler {
|
|
|
594
598
|
|
|
595
599
|
interface MessageSender {
|
|
596
600
|
sendTextToChat(chatId: string, text: string): Promise<void>;
|
|
601
|
+
sendImageToChat?(chatId: string, imagePath: string): Promise<void>;
|
|
597
602
|
replyTextToMessage?(messageId: string, text: string): Promise<void>;
|
|
598
603
|
addReactionToMessage?(messageId: string, emojiType: string): Promise<void>;
|
|
599
604
|
}
|
|
@@ -621,6 +626,14 @@ interface FeishuClientLike {
|
|
|
621
626
|
};
|
|
622
627
|
}) => Promise<unknown>;
|
|
623
628
|
};
|
|
629
|
+
image?: {
|
|
630
|
+
create(payload: {
|
|
631
|
+
data: {
|
|
632
|
+
image_type: "message";
|
|
633
|
+
image: Buffer;
|
|
634
|
+
};
|
|
635
|
+
}): Promise<unknown>;
|
|
636
|
+
};
|
|
624
637
|
messageReaction?: {
|
|
625
638
|
create(payload: {
|
|
626
639
|
path: {
|
|
@@ -663,6 +676,7 @@ declare class FeishuMessageSender implements MessageSender {
|
|
|
663
676
|
constructor(client: FeishuClientLike);
|
|
664
677
|
static fromConfig(config: AppConfig, secrets: AppSecrets): FeishuMessageSender;
|
|
665
678
|
sendTextToChat(chatId: string, text: string): Promise<void>;
|
|
679
|
+
sendImageToChat(chatId: string, imagePath: string): Promise<void>;
|
|
666
680
|
replyTextToMessage(messageId: string, text: string): Promise<void>;
|
|
667
681
|
addReactionToMessage(messageId: string, emojiType: string): Promise<void>;
|
|
668
682
|
}
|
|
@@ -731,7 +745,7 @@ interface FeishuGatewayOptions {
|
|
|
731
745
|
cronJobProcessor?: {
|
|
732
746
|
database: SqliteDatabase;
|
|
733
747
|
model: ChatModel;
|
|
734
|
-
sender: Pick<MessageSender, "sendTextToChat">;
|
|
748
|
+
sender: Pick<MessageSender, "sendTextToChat" | "sendImageToChat">;
|
|
735
749
|
};
|
|
736
750
|
cronJobScheduler?: CronJobScheduler;
|
|
737
751
|
wsClientFactory?: (params: {
|
|
@@ -910,6 +924,7 @@ declare class OpenAICompatibleEmbeddingModel implements EmbeddingModel {
|
|
|
910
924
|
constructor(options: OpenAICompatibleEmbeddingOptions);
|
|
911
925
|
embed(text: string): Promise<number[]>;
|
|
912
926
|
embedBatch(texts: string[]): Promise<number[][]>;
|
|
927
|
+
private fetchEmbeddingBatch;
|
|
913
928
|
}
|
|
914
929
|
declare function createChatModel(config: AppConfig, secrets: AppSecrets): OpenAICompatibleChatModel;
|
|
915
930
|
declare function createEmbeddingModel(config: AppConfig, secrets: AppSecrets): OpenAICompatibleEmbeddingModel;
|
package/dist/index.js
CHANGED
|
@@ -362,6 +362,7 @@ var CronJobRepository = class {
|
|
|
362
362
|
create(input) {
|
|
363
363
|
const schedule = input.schedule.trim();
|
|
364
364
|
const prompt = input.prompt.trim();
|
|
365
|
+
const imageFileName = input.imageFileName?.trim();
|
|
365
366
|
if (!isValidCronSchedule(schedule)) {
|
|
366
367
|
throw new Error("cron \u8868\u8FBE\u5F0F\u65E0\u6548\u3002");
|
|
367
368
|
}
|
|
@@ -379,6 +380,7 @@ var CronJobRepository = class {
|
|
|
379
380
|
createdByOpenId: input.createdByOpenId,
|
|
380
381
|
schedule,
|
|
381
382
|
prompt,
|
|
383
|
+
...imageFileName ? { imageFileName } : {},
|
|
382
384
|
status: "active",
|
|
383
385
|
nextRunAt: nextRunAt.toISOString(),
|
|
384
386
|
createdAt: now.toISOString(),
|
|
@@ -387,15 +389,18 @@ var CronJobRepository = class {
|
|
|
387
389
|
this.database.prepare(
|
|
388
390
|
`
|
|
389
391
|
INSERT INTO cron_jobs (
|
|
390
|
-
id, chat_id, created_by_open_id, schedule, prompt, status,
|
|
392
|
+
id, chat_id, created_by_open_id, schedule, prompt, image_file_name, status,
|
|
391
393
|
last_run_at, next_run_at, last_error, created_at, updated_at
|
|
392
394
|
)
|
|
393
395
|
VALUES (
|
|
394
|
-
@id, @chatId, @createdByOpenId, @schedule, @prompt, @status,
|
|
396
|
+
@id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName, @status,
|
|
395
397
|
NULL, @nextRunAt, NULL, @createdAt, @updatedAt
|
|
396
398
|
)
|
|
397
399
|
`
|
|
398
|
-
).run(
|
|
400
|
+
).run({
|
|
401
|
+
...record,
|
|
402
|
+
imageFileName: record.imageFileName ?? null
|
|
403
|
+
});
|
|
399
404
|
return record;
|
|
400
405
|
}
|
|
401
406
|
get(id) {
|
|
@@ -420,6 +425,7 @@ var CronJobRepository = class {
|
|
|
420
425
|
created_by_open_id AS createdByOpenId,
|
|
421
426
|
schedule,
|
|
422
427
|
prompt,
|
|
428
|
+
image_file_name AS imageFileName,
|
|
423
429
|
status,
|
|
424
430
|
last_run_at AS lastRunAt,
|
|
425
431
|
next_run_at AS nextRunAt,
|
|
@@ -438,6 +444,7 @@ var CronJobRepository = class {
|
|
|
438
444
|
createdByOpenId: row.createdByOpenId ?? void 0,
|
|
439
445
|
schedule: row.schedule,
|
|
440
446
|
prompt: row.prompt,
|
|
447
|
+
imageFileName: row.imageFileName ?? void 0,
|
|
441
448
|
status: row.status,
|
|
442
449
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
443
450
|
nextRunAt: row.nextRunAt,
|
|
@@ -511,6 +518,7 @@ var CronJobRepository = class {
|
|
|
511
518
|
created_by_open_id AS createdByOpenId,
|
|
512
519
|
schedule,
|
|
513
520
|
prompt,
|
|
521
|
+
image_file_name AS imageFileName,
|
|
514
522
|
status,
|
|
515
523
|
last_run_at AS lastRunAt,
|
|
516
524
|
next_run_at AS nextRunAt,
|
|
@@ -529,6 +537,7 @@ var CronJobRepository = class {
|
|
|
529
537
|
createdByOpenId: row.createdByOpenId ?? void 0,
|
|
530
538
|
schedule: row.schedule,
|
|
531
539
|
prompt: row.prompt,
|
|
540
|
+
imageFileName: row.imageFileName ?? void 0,
|
|
532
541
|
status: row.status,
|
|
533
542
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
534
543
|
nextRunAt: row.nextRunAt,
|
|
@@ -559,6 +568,12 @@ function createCronJobScheduler(options) {
|
|
|
559
568
|
try {
|
|
560
569
|
const text = await options.generateMessage(job, startedAt);
|
|
561
570
|
await options.sendTextToChat(job.chatId, text);
|
|
571
|
+
if (job.imageFileName) {
|
|
572
|
+
if (!options.sendImageToChat) {
|
|
573
|
+
throw new Error("\u5F53\u524D\u5B9A\u65F6\u4EFB\u52A1\u8FD0\u884C\u73AF\u5883\u4E0D\u652F\u6301\u53D1\u9001\u56FE\u7247\u3002");
|
|
574
|
+
}
|
|
575
|
+
await options.sendImageToChat(job.chatId, job.imageFileName);
|
|
576
|
+
}
|
|
562
577
|
options.repository.markSuccess(job.id, startedAt);
|
|
563
578
|
} catch (error) {
|
|
564
579
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -599,6 +614,17 @@ function readString(input, key) {
|
|
|
599
614
|
}
|
|
600
615
|
return value.trim();
|
|
601
616
|
}
|
|
617
|
+
function readOptionalString(input, key) {
|
|
618
|
+
const value = typeof input === "object" && input !== null && key in input ? input[key] : void 0;
|
|
619
|
+
if (value === void 0 || value === null) {
|
|
620
|
+
return void 0;
|
|
621
|
+
}
|
|
622
|
+
if (typeof value !== "string") {
|
|
623
|
+
throw new Error(`${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002`);
|
|
624
|
+
}
|
|
625
|
+
const trimmed = value.trim();
|
|
626
|
+
return trimmed || void 0;
|
|
627
|
+
}
|
|
602
628
|
function createCronJobTools(input) {
|
|
603
629
|
return [
|
|
604
630
|
{
|
|
@@ -614,6 +640,10 @@ function createCronJobTools(input) {
|
|
|
614
640
|
prompt: {
|
|
615
641
|
type: "string",
|
|
616
642
|
description: "Prompt used later to generate the scheduled message."
|
|
643
|
+
},
|
|
644
|
+
imageFileName: {
|
|
645
|
+
type: "string",
|
|
646
|
+
description: "Optional image filename already stored from the current chat, for example om_xxx-image.jpg."
|
|
617
647
|
}
|
|
618
648
|
},
|
|
619
649
|
required: ["schedule", "prompt"],
|
|
@@ -624,7 +654,8 @@ function createCronJobTools(input) {
|
|
|
624
654
|
chatId: input.chatId,
|
|
625
655
|
createdByOpenId: input.createdByOpenId,
|
|
626
656
|
schedule: readString(rawInput, "schedule"),
|
|
627
|
-
prompt: readString(rawInput, "prompt")
|
|
657
|
+
prompt: readString(rawInput, "prompt"),
|
|
658
|
+
imageFileName: readOptionalString(rawInput, "imageFileName")
|
|
628
659
|
});
|
|
629
660
|
return JSON.stringify({ ok: true, job });
|
|
630
661
|
}
|
|
@@ -954,12 +985,17 @@ function migrateDatabase(database) {
|
|
|
954
985
|
next_run_at TEXT NOT NULL,
|
|
955
986
|
last_error TEXT,
|
|
956
987
|
created_at TEXT NOT NULL,
|
|
957
|
-
updated_at TEXT NOT NULL
|
|
988
|
+
updated_at TEXT NOT NULL,
|
|
989
|
+
image_file_name TEXT
|
|
958
990
|
);
|
|
959
991
|
|
|
960
992
|
CREATE INDEX IF NOT EXISTS cron_jobs_chat_status_idx ON cron_jobs(chat_id, status, updated_at);
|
|
961
993
|
CREATE INDEX IF NOT EXISTS cron_jobs_due_idx ON cron_jobs(status, next_run_at);
|
|
962
994
|
`);
|
|
995
|
+
const cronJobColumns = database.prepare("PRAGMA table_info(cron_jobs)").all();
|
|
996
|
+
if (!cronJobColumns.some((column) => column.name === "image_file_name")) {
|
|
997
|
+
database.prepare("ALTER TABLE cron_jobs ADD COLUMN image_file_name TEXT").run();
|
|
998
|
+
}
|
|
963
999
|
}
|
|
964
1000
|
|
|
965
1001
|
// src/doctor/checks.ts
|
|
@@ -1372,6 +1408,7 @@ function getGatewayStatus(config, secrets) {
|
|
|
1372
1408
|
}
|
|
1373
1409
|
|
|
1374
1410
|
// src/llm/openai-compatible.ts
|
|
1411
|
+
var OPENAI_EMBEDDING_BATCH_SIZE = 64;
|
|
1375
1412
|
function normalizeBaseUrl(baseUrl) {
|
|
1376
1413
|
return baseUrl.replace(/\/+$/, "");
|
|
1377
1414
|
}
|
|
@@ -1403,12 +1440,66 @@ function toOpenAITool(tool) {
|
|
|
1403
1440
|
}
|
|
1404
1441
|
};
|
|
1405
1442
|
}
|
|
1443
|
+
function parseToolCallArguments(value) {
|
|
1444
|
+
try {
|
|
1445
|
+
return JSON.parse(value);
|
|
1446
|
+
} catch {
|
|
1447
|
+
return {};
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
function decodeDsmlValue(value, isString) {
|
|
1451
|
+
const trimmed = value.trim();
|
|
1452
|
+
if (isString) {
|
|
1453
|
+
return trimmed;
|
|
1454
|
+
}
|
|
1455
|
+
if (trimmed === "true") return true;
|
|
1456
|
+
if (trimmed === "false") return false;
|
|
1457
|
+
if (trimmed === "null") return null;
|
|
1458
|
+
const numberValue = Number(trimmed);
|
|
1459
|
+
if (trimmed && Number.isFinite(numberValue)) {
|
|
1460
|
+
return numberValue;
|
|
1461
|
+
}
|
|
1462
|
+
return trimmed;
|
|
1463
|
+
}
|
|
1464
|
+
function parseDsmlToolCalls(content) {
|
|
1465
|
+
if (!content?.includes("DSML")) {
|
|
1466
|
+
return [];
|
|
1467
|
+
}
|
|
1468
|
+
const toolCalls = [];
|
|
1469
|
+
const invokePattern = /<||DSML||invoke\s+name="([^"]+)"\s*>([\s\S]*?)<\/||DSML||invoke>/g;
|
|
1470
|
+
const parameterPattern = /<||DSML||parameter\s+name="([^"]+)"\s+string="(true|false)"\s*>([\s\S]*?)<\/||DSML||parameter>/g;
|
|
1471
|
+
for (const invoke of content.matchAll(invokePattern)) {
|
|
1472
|
+
const name = invoke[1];
|
|
1473
|
+
if (!name) {
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
const input = {};
|
|
1477
|
+
const body = invoke[2] ?? "";
|
|
1478
|
+
for (const parameter of body.matchAll(parameterPattern)) {
|
|
1479
|
+
const parameterName = parameter[1];
|
|
1480
|
+
if (!parameterName) {
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
input[parameterName] = decodeDsmlValue(parameter[3] ?? "", parameter[2] === "true");
|
|
1484
|
+
}
|
|
1485
|
+
toolCalls.push({
|
|
1486
|
+
id: `dsml_${toolCalls.length + 1}`,
|
|
1487
|
+
name,
|
|
1488
|
+
input
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
return toolCalls;
|
|
1492
|
+
}
|
|
1406
1493
|
function parseToolCalls(message) {
|
|
1407
|
-
|
|
1494
|
+
const standardToolCalls = message?.tool_calls?.map((toolCall) => ({
|
|
1408
1495
|
id: toolCall.id,
|
|
1409
1496
|
name: toolCall.function.name,
|
|
1410
|
-
input:
|
|
1497
|
+
input: parseToolCallArguments(toolCall.function.arguments)
|
|
1411
1498
|
})) ?? [];
|
|
1499
|
+
return standardToolCalls.length > 0 ? standardToolCalls : parseDsmlToolCalls(message?.content);
|
|
1500
|
+
}
|
|
1501
|
+
function isDsmlToolCallContent(content) {
|
|
1502
|
+
return parseDsmlToolCalls(content).length > 0;
|
|
1412
1503
|
}
|
|
1413
1504
|
var OpenAICompatibleChatModel = class {
|
|
1414
1505
|
constructor(options) {
|
|
@@ -1467,9 +1558,10 @@ var OpenAICompatibleChatModel = class {
|
|
|
1467
1558
|
}
|
|
1468
1559
|
const data = await response.json();
|
|
1469
1560
|
const message = data.choices?.[0]?.message;
|
|
1561
|
+
const toolCalls = parseToolCalls(message);
|
|
1470
1562
|
return {
|
|
1471
|
-
content: message?.content ?? "",
|
|
1472
|
-
toolCalls
|
|
1563
|
+
content: toolCalls.length > 0 && isDsmlToolCallContent(message?.content) ? "" : message?.content ?? "",
|
|
1564
|
+
toolCalls,
|
|
1473
1565
|
reasoningContent: message?.reasoning_content ?? void 0
|
|
1474
1566
|
};
|
|
1475
1567
|
}
|
|
@@ -1487,6 +1579,13 @@ var OpenAICompatibleEmbeddingModel = class {
|
|
|
1487
1579
|
if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {
|
|
1488
1580
|
throw new Error("Embedding \u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
|
|
1489
1581
|
}
|
|
1582
|
+
const vectors = [];
|
|
1583
|
+
for (let index = 0; index < texts.length; index += OPENAI_EMBEDDING_BATCH_SIZE) {
|
|
1584
|
+
vectors.push(...await this.fetchEmbeddingBatch(texts.slice(index, index + OPENAI_EMBEDDING_BATCH_SIZE)));
|
|
1585
|
+
}
|
|
1586
|
+
return vectors;
|
|
1587
|
+
}
|
|
1588
|
+
async fetchEmbeddingBatch(texts) {
|
|
1490
1589
|
const response = await fetch(`${normalizeBaseUrl(this.options.baseUrl)}/embeddings`, {
|
|
1491
1590
|
method: "POST",
|
|
1492
1591
|
headers: {
|
|
@@ -1710,6 +1809,9 @@ var MessageRepository = class {
|
|
|
1710
1809
|
throw new Error("\u539F\u59CB\u56FE\u7247\u6D88\u606F\u4E0D\u5B58\u5728\u3002");
|
|
1711
1810
|
}
|
|
1712
1811
|
const derivedPlatformMessageId = `${source.platformMessageId}:image-summary:${input.imageKey}`;
|
|
1812
|
+
const imageFileName = input.imageFileName?.trim();
|
|
1813
|
+
const summaryText = imageFileName ? `[\u56FE\u7247\u8F6C\u8FF0] \u6587\u4EF6\u540D\uFF1A${imageFileName}
|
|
1814
|
+
${input.summary.trim()}` : `[\u56FE\u7247\u8F6C\u8FF0] ${input.summary.trim()}`;
|
|
1713
1815
|
return this.ingest({
|
|
1714
1816
|
platform: source.platform,
|
|
1715
1817
|
platformChatId: source.platformChatId,
|
|
@@ -1718,12 +1820,13 @@ var MessageRepository = class {
|
|
|
1718
1820
|
senderId: source.senderId,
|
|
1719
1821
|
senderName: source.senderName,
|
|
1720
1822
|
messageType: "image_summary",
|
|
1721
|
-
text:
|
|
1823
|
+
text: summaryText,
|
|
1722
1824
|
sentAt: source.sentAt,
|
|
1723
1825
|
rawPayload: {
|
|
1724
1826
|
derivedFromMessageId: input.sourceMessageId,
|
|
1725
1827
|
sourceAttachmentKind: "image",
|
|
1726
1828
|
sourceResourceKey: input.imageKey,
|
|
1829
|
+
...imageFileName ? { imageFileName } : {},
|
|
1727
1830
|
multimodalModel: input.multimodalModel,
|
|
1728
1831
|
isMeaningful: true,
|
|
1729
1832
|
...input.reason?.trim() ? { reason: input.reason.trim() } : {},
|
|
@@ -3024,7 +3127,7 @@ async function summarizeEpisodeWindow(window, model, now) {
|
|
|
3024
3127
|
const summary = await model.complete([
|
|
3025
3128
|
{
|
|
3026
3129
|
role: "system",
|
|
3027
|
-
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"
|
|
3130
|
+
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"
|
|
3028
3131
|
},
|
|
3029
3132
|
{
|
|
3030
3133
|
role: "user",
|
|
@@ -3055,6 +3158,7 @@ async function processEpisodesNow(input) {
|
|
|
3055
3158
|
|
|
3056
3159
|
// src/feishu/gateway.ts
|
|
3057
3160
|
import * as lark2 from "@larksuiteoapi/node-sdk";
|
|
3161
|
+
import path12 from "path";
|
|
3058
3162
|
|
|
3059
3163
|
// src/gateway/indexing-scheduler.ts
|
|
3060
3164
|
function createIndexingScheduler(options) {
|
|
@@ -3164,12 +3268,20 @@ function parseExactNumber2(field, min, max) {
|
|
|
3164
3268
|
}
|
|
3165
3269
|
|
|
3166
3270
|
// src/rag/indexer.ts
|
|
3271
|
+
var EMBEDDING_INDEX_BATCH_SIZE = 64;
|
|
3167
3272
|
async function indexMessageChunks(input) {
|
|
3168
3273
|
const chunks = input.messageIds ? input.messages.listMessageChunksByMessageIds(input.messageIds, input.limit ?? 1e4) : input.messages.listAllMessageChunks(input.limit ?? 1e4);
|
|
3169
3274
|
if (chunks.length === 0) {
|
|
3170
3275
|
return { chunks: 0, vectors: 0 };
|
|
3171
3276
|
}
|
|
3172
|
-
const vectors =
|
|
3277
|
+
const vectors = [];
|
|
3278
|
+
for (let index = 0; index < chunks.length; index += EMBEDDING_INDEX_BATCH_SIZE) {
|
|
3279
|
+
vectors.push(
|
|
3280
|
+
...await input.embedding.embedBatch(
|
|
3281
|
+
chunks.slice(index, index + EMBEDDING_INDEX_BATCH_SIZE).map((chunk) => chunk.text)
|
|
3282
|
+
)
|
|
3283
|
+
);
|
|
3284
|
+
}
|
|
3173
3285
|
const records = [];
|
|
3174
3286
|
for (const [index, chunk] of chunks.entries()) {
|
|
3175
3287
|
const vector = vectors[index];
|
|
@@ -3440,6 +3552,7 @@ var ImageMultimodalTaskRepository = class {
|
|
|
3440
3552
|
};
|
|
3441
3553
|
|
|
3442
3554
|
// src/multimodal/worker.ts
|
|
3555
|
+
import path11 from "path";
|
|
3443
3556
|
var ImageMultimodalWorker = class {
|
|
3444
3557
|
constructor(options) {
|
|
3445
3558
|
this.options = options;
|
|
@@ -3466,9 +3579,11 @@ var ImageMultimodalWorker = class {
|
|
|
3466
3579
|
throw error;
|
|
3467
3580
|
}
|
|
3468
3581
|
try {
|
|
3582
|
+
const imageFileName = path11.basename(running.storedPath);
|
|
3469
3583
|
const described = await this.options.model.describeImage({
|
|
3470
3584
|
imagePath: running.storedPath,
|
|
3471
|
-
mimeType: running.mimeType
|
|
3585
|
+
mimeType: running.mimeType,
|
|
3586
|
+
context: `\u56FE\u7247\u6587\u4EF6\u540D\uFF1A${imageFileName}`
|
|
3472
3587
|
});
|
|
3473
3588
|
if (!described.isMeaningful) {
|
|
3474
3589
|
this.options.tasks.markSkipped(running.id, described.reason || "\u591A\u6A21\u6001\u6A21\u578B\u5224\u5B9A\u56FE\u7247\u65E0\u610F\u4E49\u3002");
|
|
@@ -3478,6 +3593,7 @@ var ImageMultimodalWorker = class {
|
|
|
3478
3593
|
const derivedMessageId = this.options.messages.createImageSummaryMessage({
|
|
3479
3594
|
sourceMessageId: running.sourceMessageId,
|
|
3480
3595
|
imageKey: running.imageKey,
|
|
3596
|
+
imageFileName,
|
|
3481
3597
|
summary: described.summary,
|
|
3482
3598
|
reason: described.reason,
|
|
3483
3599
|
multimodalModel: this.options.multimodalModelName,
|
|
@@ -3628,11 +3744,14 @@ function stripMentions(text, mentions) {
|
|
|
3628
3744
|
}
|
|
3629
3745
|
return result.replace(/@/g, " ").replace(/\s+/g, " ").trim();
|
|
3630
3746
|
}
|
|
3631
|
-
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`;
|
|
3747
|
+
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`;
|
|
3632
3748
|
var DEFAULT_MAX_MODEL_TURNS = 4;
|
|
3633
3749
|
var DEFAULT_MAX_TOOL_CALLS = 8;
|
|
3634
3750
|
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";
|
|
3635
3751
|
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";
|
|
3752
|
+
function containsRawToolCallMarkup(content) {
|
|
3753
|
+
return /<||DSML||tool_calls>|<||DSML||invoke\s+name=|<tool_call>|<tool_calls>/i.test(content);
|
|
3754
|
+
}
|
|
3636
3755
|
function toToolResultContent(value) {
|
|
3637
3756
|
if (typeof value === "string") return value;
|
|
3638
3757
|
return JSON.stringify(value);
|
|
@@ -3675,6 +3794,7 @@ async function runFeishuToolLoop(input) {
|
|
|
3675
3794
|
let toolCallsUsed = 0;
|
|
3676
3795
|
for (let turn = 0; turn < maxModelTurns; turn += 1) {
|
|
3677
3796
|
const assistantResult = await input.model.completeWithTools(messages, input.tools);
|
|
3797
|
+
const hasRawToolCallMarkup = containsRawToolCallMarkup(assistantResult.content);
|
|
3678
3798
|
messages.push({
|
|
3679
3799
|
role: "assistant",
|
|
3680
3800
|
content: assistantResult.content,
|
|
@@ -3682,6 +3802,9 @@ async function runFeishuToolLoop(input) {
|
|
|
3682
3802
|
reasoningContent: assistantResult.reasoningContent
|
|
3683
3803
|
});
|
|
3684
3804
|
if (assistantResult.toolCalls.length === 0) {
|
|
3805
|
+
if (hasRawToolCallMarkup) {
|
|
3806
|
+
break;
|
|
3807
|
+
}
|
|
3685
3808
|
return assistantResult.content || FEISHU_TOOL_LOOP_FALLBACK;
|
|
3686
3809
|
}
|
|
3687
3810
|
for (const toolCall of assistantResult.toolCalls) {
|
|
@@ -3794,7 +3917,7 @@ var FeishuQuestionHandler = class {
|
|
|
3794
3917
|
}
|
|
3795
3918
|
if (this.options.sender.addReactionToMessage) {
|
|
3796
3919
|
try {
|
|
3797
|
-
await this.options.sender.addReactionToMessage(messageId, this.options.thinkingEmojiType ?? "
|
|
3920
|
+
await this.options.sender.addReactionToMessage(messageId, this.options.thinkingEmojiType ?? "OK");
|
|
3798
3921
|
return;
|
|
3799
3922
|
} catch (error) {
|
|
3800
3923
|
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)}`);
|
|
@@ -3867,9 +3990,22 @@ var FeishuQuestionHandler = class {
|
|
|
3867
3990
|
|
|
3868
3991
|
// src/feishu/sender.ts
|
|
3869
3992
|
import * as lark from "@larksuiteoapi/node-sdk";
|
|
3993
|
+
import fs9 from "fs/promises";
|
|
3870
3994
|
function mapDomain(domain) {
|
|
3871
3995
|
return domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
|
|
3872
3996
|
}
|
|
3997
|
+
function extractImageKey(response) {
|
|
3998
|
+
const data = response && typeof response === "object" ? response : {};
|
|
3999
|
+
const direct = data.image_key;
|
|
4000
|
+
if (typeof direct === "string" && direct.trim()) {
|
|
4001
|
+
return direct.trim();
|
|
4002
|
+
}
|
|
4003
|
+
const nested = data.data && typeof data.data === "object" ? data.data.image_key : void 0;
|
|
4004
|
+
if (typeof nested === "string" && nested.trim()) {
|
|
4005
|
+
return nested.trim();
|
|
4006
|
+
}
|
|
4007
|
+
throw new Error("\u98DE\u4E66\u56FE\u7247\u4E0A\u4F20\u54CD\u5E94\u7F3A\u5C11 image_key\u3002");
|
|
4008
|
+
}
|
|
3873
4009
|
var FeishuMessageSender = class _FeishuMessageSender {
|
|
3874
4010
|
constructor(client) {
|
|
3875
4011
|
this.client = client;
|
|
@@ -3906,6 +4042,39 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
3906
4042
|
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
3907
4043
|
}
|
|
3908
4044
|
}
|
|
4045
|
+
async sendImageToChat(chatId, imagePath) {
|
|
4046
|
+
const imageCreate = this.client.im.v1?.image?.create;
|
|
4047
|
+
if (!imageCreate) {
|
|
4048
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u56FE\u7247\u4E0A\u4F20\u63A5\u53E3\u3002");
|
|
4049
|
+
}
|
|
4050
|
+
const image = await fs9.readFile(imagePath);
|
|
4051
|
+
const uploaded = await imageCreate({
|
|
4052
|
+
data: {
|
|
4053
|
+
image_type: "message",
|
|
4054
|
+
image
|
|
4055
|
+
}
|
|
4056
|
+
});
|
|
4057
|
+
const imageKey = extractImageKey(uploaded);
|
|
4058
|
+
const payload = {
|
|
4059
|
+
data: {
|
|
4060
|
+
receive_id: chatId,
|
|
4061
|
+
msg_type: "image",
|
|
4062
|
+
content: JSON.stringify({ image_key: imageKey })
|
|
4063
|
+
},
|
|
4064
|
+
params: {
|
|
4065
|
+
receive_id_type: "chat_id"
|
|
4066
|
+
}
|
|
4067
|
+
};
|
|
4068
|
+
if (this.client.im.v1?.message.create) {
|
|
4069
|
+
await this.client.im.v1.message.create(payload);
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4072
|
+
if (this.client.im.message?.create) {
|
|
4073
|
+
await this.client.im.message.create(payload);
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4077
|
+
}
|
|
3909
4078
|
async replyTextToMessage(messageId, text) {
|
|
3910
4079
|
const payload = {
|
|
3911
4080
|
path: {
|
|
@@ -4046,6 +4215,13 @@ function createFeishuEventDispatcher(options) {
|
|
|
4046
4215
|
}
|
|
4047
4216
|
});
|
|
4048
4217
|
}
|
|
4218
|
+
function resolveFeishuImagePath(config, imageFileName) {
|
|
4219
|
+
const fileName = path12.basename(imageFileName.trim());
|
|
4220
|
+
if (!fileName || fileName !== imageFileName.trim()) {
|
|
4221
|
+
throw new Error("\u56FE\u7247\u6587\u4EF6\u540D\u65E0\u6548\u3002");
|
|
4222
|
+
}
|
|
4223
|
+
return path12.join(resolveHomePath(config.storage.dataDir), "files", "feishu", fileName);
|
|
4224
|
+
}
|
|
4049
4225
|
function createFeishuGateway(options) {
|
|
4050
4226
|
assertFeishuConfig(options.config, options.secrets);
|
|
4051
4227
|
const wsClient = options.wsClientFactory?.({
|
|
@@ -4091,6 +4267,10 @@ function createFeishuGateway(options) {
|
|
|
4091
4267
|
const cronJobScheduler = options.cronJobScheduler ?? (options.cronJobProcessor ? createCronJobScheduler({
|
|
4092
4268
|
repository: new CronJobRepository(options.cronJobProcessor.database),
|
|
4093
4269
|
sendTextToChat: (chatId, text) => options.cronJobProcessor.sender.sendTextToChat(chatId, text),
|
|
4270
|
+
sendImageToChat: options.cronJobProcessor.sender.sendImageToChat ? (chatId, imageFileName) => options.cronJobProcessor.sender.sendImageToChat(
|
|
4271
|
+
chatId,
|
|
4272
|
+
resolveFeishuImagePath(options.config, imageFileName)
|
|
4273
|
+
) : void 0,
|
|
4094
4274
|
generateMessage: async (job, now) => {
|
|
4095
4275
|
const { tools, close } = await createAgenticRagSearchTools({
|
|
4096
4276
|
config: options.config,
|
|
@@ -4278,8 +4458,8 @@ function normalizeFeishuReceiveMessageEvent(payload) {
|
|
|
4278
4458
|
|
|
4279
4459
|
// src/feishu/resource-downloader.ts
|
|
4280
4460
|
import * as lark3 from "@larksuiteoapi/node-sdk";
|
|
4281
|
-
import
|
|
4282
|
-
import
|
|
4461
|
+
import fs10 from "fs/promises";
|
|
4462
|
+
import path13 from "path";
|
|
4283
4463
|
var RESOURCE_TYPE_BY_KIND = {
|
|
4284
4464
|
file: "file",
|
|
4285
4465
|
image: "image",
|
|
@@ -4317,10 +4497,10 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
4317
4497
|
}
|
|
4318
4498
|
async download(input) {
|
|
4319
4499
|
const resourceType = RESOURCE_TYPE_BY_KIND[input.attachment.kind];
|
|
4320
|
-
const targetDir =
|
|
4321
|
-
await
|
|
4500
|
+
const targetDir = path13.join(this.dataDir, "files", "feishu");
|
|
4501
|
+
await fs10.mkdir(targetDir, { recursive: true });
|
|
4322
4502
|
const fileName = buildStoredFileName(input);
|
|
4323
|
-
const storedPath =
|
|
4503
|
+
const storedPath = path13.join(targetDir, fileName);
|
|
4324
4504
|
const payload = {
|
|
4325
4505
|
params: { type: resourceType },
|
|
4326
4506
|
path: { message_id: input.messageId, file_key: input.attachment.fileKey }
|
|
@@ -4343,29 +4523,29 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
4343
4523
|
|
|
4344
4524
|
// src/files/ingest.ts
|
|
4345
4525
|
import crypto7 from "crypto";
|
|
4346
|
-
import
|
|
4347
|
-
import
|
|
4526
|
+
import fs12 from "fs/promises";
|
|
4527
|
+
import path15 from "path";
|
|
4348
4528
|
|
|
4349
4529
|
// src/files/parser.ts
|
|
4350
|
-
import
|
|
4351
|
-
import
|
|
4530
|
+
import fs11 from "fs/promises";
|
|
4531
|
+
import path14 from "path";
|
|
4352
4532
|
import mammoth from "mammoth";
|
|
4353
4533
|
import { PDFParse } from "pdf-parse";
|
|
4354
4534
|
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".markdown", ".json", ".csv", ".tsv", ".log"]);
|
|
4355
4535
|
var DOCX_EXTENSIONS = /* @__PURE__ */ new Set([".docx"]);
|
|
4356
4536
|
var PDF_EXTENSIONS = /* @__PURE__ */ new Set([".pdf"]);
|
|
4357
4537
|
function isSupportedParseFile(filePath) {
|
|
4358
|
-
const extension =
|
|
4538
|
+
const extension = path14.extname(filePath).toLowerCase();
|
|
4359
4539
|
return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);
|
|
4360
4540
|
}
|
|
4361
4541
|
function describeSupportedParseTypes() {
|
|
4362
4542
|
return "txt\u3001md\u3001json\u3001csv\u3001tsv\u3001log\u3001docx\u3001pdf";
|
|
4363
4543
|
}
|
|
4364
4544
|
async function parseFileToText(filePath) {
|
|
4365
|
-
const extension =
|
|
4545
|
+
const extension = path14.extname(filePath).toLowerCase();
|
|
4366
4546
|
if (TEXT_EXTENSIONS.has(extension)) {
|
|
4367
4547
|
return {
|
|
4368
|
-
text: await
|
|
4548
|
+
text: await fs11.readFile(filePath, "utf8"),
|
|
4369
4549
|
parser: "text",
|
|
4370
4550
|
warnings: []
|
|
4371
4551
|
};
|
|
@@ -4379,7 +4559,7 @@ async function parseFileToText(filePath) {
|
|
|
4379
4559
|
};
|
|
4380
4560
|
}
|
|
4381
4561
|
if (PDF_EXTENSIONS.has(extension)) {
|
|
4382
|
-
const buffer = await
|
|
4562
|
+
const buffer = await fs11.readFile(filePath);
|
|
4383
4563
|
const parser = new PDFParse({ data: buffer });
|
|
4384
4564
|
try {
|
|
4385
4565
|
const result = await parser.getText();
|
|
@@ -4401,7 +4581,7 @@ function isSupportedTextFile(filePath) {
|
|
|
4401
4581
|
}
|
|
4402
4582
|
function ensureSupportedTextFile(filePath) {
|
|
4403
4583
|
if (!isSupportedTextFile(filePath)) {
|
|
4404
|
-
const extension =
|
|
4584
|
+
const extension = path15.extname(filePath).toLowerCase();
|
|
4405
4585
|
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`);
|
|
4406
4586
|
}
|
|
4407
4587
|
}
|
|
@@ -4410,12 +4590,12 @@ function stableStoredName(sourcePath, fileName) {
|
|
|
4410
4590
|
return `${digest}-${fileName}`;
|
|
4411
4591
|
}
|
|
4412
4592
|
async function ingestLocalFile(input) {
|
|
4413
|
-
const sourcePath =
|
|
4414
|
-
const fileName =
|
|
4593
|
+
const sourcePath = path15.resolve(input.filePath);
|
|
4594
|
+
const fileName = path15.basename(sourcePath);
|
|
4415
4595
|
const jobId = input.jobs?.start({ sourcePath, fileName });
|
|
4416
4596
|
try {
|
|
4417
4597
|
ensureSupportedTextFile(sourcePath);
|
|
4418
|
-
const stat = await
|
|
4598
|
+
const stat = await fs12.stat(sourcePath);
|
|
4419
4599
|
if (!stat.isFile()) {
|
|
4420
4600
|
throw new Error(`\u4E0D\u662F\u6587\u4EF6\uFF1A${sourcePath}`);
|
|
4421
4601
|
}
|
|
@@ -4424,10 +4604,10 @@ async function ingestLocalFile(input) {
|
|
|
4424
4604
|
if (!text) {
|
|
4425
4605
|
throw new Error(`\u6587\u4EF6\u6CA1\u6709\u53EF\u7D22\u5F15\u6587\u672C\uFF1A${sourcePath}`);
|
|
4426
4606
|
}
|
|
4427
|
-
const fileDir =
|
|
4428
|
-
await
|
|
4429
|
-
const storedPath =
|
|
4430
|
-
await
|
|
4607
|
+
const fileDir = path15.join(resolveHomePath(input.config.storage.dataDir), "files");
|
|
4608
|
+
await fs12.mkdir(fileDir, { recursive: true });
|
|
4609
|
+
const storedPath = path15.join(fileDir, stableStoredName(sourcePath, fileName));
|
|
4610
|
+
await fs12.copyFile(sourcePath, storedPath);
|
|
4431
4611
|
const messageId = input.messages.ingest({
|
|
4432
4612
|
platform: "local-file",
|
|
4433
4613
|
platformChatId: "local-files",
|