opencode-lark 0.1.1
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/CHANGELOG.md +50 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/README.zh-CN.md +324 -0
- package/bin/opencode-lark.js +2 -0
- package/dist/channel/base-plugin.d.ts +31 -0
- package/dist/channel/base-plugin.d.ts.map +1 -0
- package/dist/channel/base-plugin.js +42 -0
- package/dist/channel/base-plugin.js.map +1 -0
- package/dist/channel/feishu/feishu-plugin.d.ts +36 -0
- package/dist/channel/feishu/feishu-plugin.d.ts.map +1 -0
- package/dist/channel/feishu/feishu-plugin.js +149 -0
- package/dist/channel/feishu/feishu-plugin.js.map +1 -0
- package/dist/channel/feishu/index.d.ts +2 -0
- package/dist/channel/feishu/index.d.ts.map +1 -0
- package/dist/channel/feishu/index.js +2 -0
- package/dist/channel/feishu/index.js.map +1 -0
- package/dist/channel/index.d.ts +4 -0
- package/dist/channel/index.d.ts.map +1 -0
- package/dist/channel/index.js +4 -0
- package/dist/channel/index.js.map +1 -0
- package/dist/channel/manager.d.ts +37 -0
- package/dist/channel/manager.d.ts.map +1 -0
- package/dist/channel/manager.js +68 -0
- package/dist/channel/manager.js.map +1 -0
- package/dist/channel/mock/mock-plugin.d.ts +24 -0
- package/dist/channel/mock/mock-plugin.d.ts.map +1 -0
- package/dist/channel/mock/mock-plugin.js +42 -0
- package/dist/channel/mock/mock-plugin.js.map +1 -0
- package/dist/channel/types.d.ts +226 -0
- package/dist/channel/types.d.ts.map +1 -0
- package/dist/channel/types.js +7 -0
- package/dist/channel/types.js.map +1 -0
- package/dist/cron/cron-service.d.ts +40 -0
- package/dist/cron/cron-service.d.ts.map +1 -0
- package/dist/cron/cron-service.js +140 -0
- package/dist/cron/cron-service.js.map +1 -0
- package/dist/cron/heartbeat.d.ts +30 -0
- package/dist/cron/heartbeat.d.ts.map +1 -0
- package/dist/cron/heartbeat.js +76 -0
- package/dist/cron/heartbeat.js.map +1 -0
- package/dist/feishu/api-client.d.ts +19 -0
- package/dist/feishu/api-client.d.ts.map +1 -0
- package/dist/feishu/api-client.js +98 -0
- package/dist/feishu/api-client.js.map +1 -0
- package/dist/feishu/card-builder.d.ts +10 -0
- package/dist/feishu/card-builder.d.ts.map +1 -0
- package/dist/feishu/card-builder.js +74 -0
- package/dist/feishu/card-builder.js.map +1 -0
- package/dist/feishu/cardkit-client.d.ts +48 -0
- package/dist/feishu/cardkit-client.d.ts.map +1 -0
- package/dist/feishu/cardkit-client.js +97 -0
- package/dist/feishu/cardkit-client.js.map +1 -0
- package/dist/feishu/message-dedup.d.ts +28 -0
- package/dist/feishu/message-dedup.d.ts.map +1 -0
- package/dist/feishu/message-dedup.js +58 -0
- package/dist/feishu/message-dedup.js.map +1 -0
- package/dist/feishu/webhook-server.d.ts +20 -0
- package/dist/feishu/webhook-server.d.ts.map +1 -0
- package/dist/feishu/webhook-server.js +111 -0
- package/dist/feishu/webhook-server.js.map +1 -0
- package/dist/feishu/ws-client.d.ts +17 -0
- package/dist/feishu/ws-client.d.ts.map +1 -0
- package/dist/feishu/ws-client.js +158 -0
- package/dist/feishu/ws-client.js.map +1 -0
- package/dist/handler/interactive-handler.d.ts +16 -0
- package/dist/handler/interactive-handler.d.ts.map +1 -0
- package/dist/handler/interactive-handler.js +86 -0
- package/dist/handler/interactive-handler.js.map +1 -0
- package/dist/handler/interactive-poller.d.ts +16 -0
- package/dist/handler/interactive-poller.d.ts.map +1 -0
- package/dist/handler/interactive-poller.js +147 -0
- package/dist/handler/interactive-poller.js.map +1 -0
- package/dist/handler/message-handler.d.ts +34 -0
- package/dist/handler/message-handler.d.ts.map +1 -0
- package/dist/handler/message-handler.js +305 -0
- package/dist/handler/message-handler.js.map +1 -0
- package/dist/handler/streaming-integration.d.ts +21 -0
- package/dist/handler/streaming-integration.d.ts.map +1 -0
- package/dist/handler/streaming-integration.js +325 -0
- package/dist/handler/streaming-integration.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +281 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/memory-manager.d.ts +16 -0
- package/dist/memory/memory-manager.d.ts.map +1 -0
- package/dist/memory/memory-manager.js +58 -0
- package/dist/memory/memory-manager.js.map +1 -0
- package/dist/session/progress-tracker.d.ts +12 -0
- package/dist/session/progress-tracker.d.ts.map +1 -0
- package/dist/session/progress-tracker.js +46 -0
- package/dist/session/progress-tracker.js.map +1 -0
- package/dist/session/session-manager.d.ts +15 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +91 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/streaming/event-processor.d.ts +74 -0
- package/dist/streaming/event-processor.d.ts.map +1 -0
- package/dist/streaming/event-processor.js +240 -0
- package/dist/streaming/event-processor.js.map +1 -0
- package/dist/streaming/session-observer.d.ts +19 -0
- package/dist/streaming/session-observer.d.ts.map +1 -0
- package/dist/streaming/session-observer.js +140 -0
- package/dist/streaming/session-observer.js.map +1 -0
- package/dist/streaming/streaming-card.d.ts +37 -0
- package/dist/streaming/streaming-card.d.ts.map +1 -0
- package/dist/streaming/streaming-card.js +139 -0
- package/dist/streaming/streaming-card.js.map +1 -0
- package/dist/streaming/subagent-card.d.ts +32 -0
- package/dist/streaming/subagent-card.d.ts.map +1 -0
- package/dist/streaming/subagent-card.js +103 -0
- package/dist/streaming/subagent-card.js.map +1 -0
- package/dist/streaming/subagent-tracker.d.ts +45 -0
- package/dist/streaming/subagent-tracker.d.ts.map +1 -0
- package/dist/streaming/subagent-tracker.js +118 -0
- package/dist/streaming/subagent-tracker.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/config.d.ts +197 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +87 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/db.d.ts +12 -0
- package/dist/utils/db.d.ts.map +1 -0
- package/dist/utils/db.js +35 -0
- package/dist/utils/db.js.map +1 -0
- package/dist/utils/event-listeners.d.ts +12 -0
- package/dist/utils/event-listeners.d.ts.map +1 -0
- package/dist/utils/event-listeners.js +21 -0
- package/dist/utils/event-listeners.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +38 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.d.ts","sourceRoot":"","sources":["../../src/cron/heartbeat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAE9D,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,eAAe,CAAA;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,SAAS,CAAI;gBAET,OAAO,EAAE,gBAAgB;IAIrC,KAAK,IAAI,IAAI;IAab,IAAI,IAAI,IAAI;YAYE,IAAI;YA4BJ,SAAS;IAiBvB,QAAQ,IAAI;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;CAGxD"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodic health check service.
|
|
3
|
+
* Pings server at intervals and reports status via Feishu on failure.
|
|
4
|
+
*/
|
|
5
|
+
export class HeartbeatService {
|
|
6
|
+
options;
|
|
7
|
+
intervalId = null;
|
|
8
|
+
running = false;
|
|
9
|
+
successCount = 0;
|
|
10
|
+
failCount = 0;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
start() {
|
|
15
|
+
if (this.running)
|
|
16
|
+
return;
|
|
17
|
+
const { logger, intervalMs } = this.options;
|
|
18
|
+
logger.info(`Heartbeat service started (interval: ${intervalMs}ms)`);
|
|
19
|
+
this.intervalId = setInterval(() => {
|
|
20
|
+
void this.tick();
|
|
21
|
+
}, intervalMs);
|
|
22
|
+
this.running = true;
|
|
23
|
+
}
|
|
24
|
+
stop() {
|
|
25
|
+
if (!this.running)
|
|
26
|
+
return;
|
|
27
|
+
if (this.intervalId !== null) {
|
|
28
|
+
clearInterval(this.intervalId);
|
|
29
|
+
this.intervalId = null;
|
|
30
|
+
}
|
|
31
|
+
this.running = false;
|
|
32
|
+
this.options.logger.info("Heartbeat service stopped");
|
|
33
|
+
}
|
|
34
|
+
async tick() {
|
|
35
|
+
const { serverUrl, logger, feishuClient, statusChatId } = this.options;
|
|
36
|
+
try {
|
|
37
|
+
const resp = await globalThis.fetch(`${serverUrl}/session/status`);
|
|
38
|
+
if (resp.ok) {
|
|
39
|
+
this.successCount++;
|
|
40
|
+
logger.info("Server healthy");
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.failCount++;
|
|
44
|
+
logger.error(`Server health check failed with HTTP ${resp.status}`);
|
|
45
|
+
if (feishuClient && statusChatId) {
|
|
46
|
+
await this.sendAlert(feishuClient, statusChatId, `HTTP ${resp.status}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
this.failCount++;
|
|
52
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53
|
+
logger.error(`Server health check failed: ${message}`);
|
|
54
|
+
if (feishuClient && statusChatId) {
|
|
55
|
+
await this.sendAlert(feishuClient, statusChatId, message);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async sendAlert(feishuClient, chatId, errorMsg) {
|
|
60
|
+
try {
|
|
61
|
+
await feishuClient.sendMessage(chatId, {
|
|
62
|
+
msg_type: "text",
|
|
63
|
+
content: JSON.stringify({
|
|
64
|
+
text: `❌ Heartbeat alert: Server health check failed (${errorMsg}). Success: ${this.successCount}, Failures: ${this.failCount}`,
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
this.options.logger.error("Failed to send heartbeat alert:", err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
getStats() {
|
|
73
|
+
return { successCount: this.successCount, failCount: this.failCount };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=heartbeat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../../src/cron/heartbeat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feishu Open Platform REST API client.
|
|
3
|
+
* Handles tenant_access_token lifecycle and core messaging APIs.
|
|
4
|
+
*/
|
|
5
|
+
import type { FeishuMessageBody, FeishuApiResponse } from "../types.js";
|
|
6
|
+
interface FeishuApiClientOptions {
|
|
7
|
+
appId: string;
|
|
8
|
+
appSecret: string;
|
|
9
|
+
}
|
|
10
|
+
export interface FeishuApiClient {
|
|
11
|
+
sendMessage(chatId: string, body: FeishuMessageBody): Promise<FeishuApiResponse>;
|
|
12
|
+
replyMessage(messageId: string, body: FeishuMessageBody): Promise<FeishuApiResponse>;
|
|
13
|
+
updateMessage(messageId: string, content: string): Promise<FeishuApiResponse>;
|
|
14
|
+
addReaction(messageId: string, emojiType: string): Promise<FeishuApiResponse>;
|
|
15
|
+
deleteReaction(messageId: string, reactionId: string): Promise<FeishuApiResponse>;
|
|
16
|
+
}
|
|
17
|
+
export declare function createFeishuApiClient(options: FeishuApiClientOptions): FeishuApiClient;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/feishu/api-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAMvE,UAAU,sBAAsB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CAClB;AAOD,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAChF,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACpF,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC7E,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC7E,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;CAClF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,eAAe,CA2HtF"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feishu Open Platform REST API client.
|
|
3
|
+
* Handles tenant_access_token lifecycle and core messaging APIs.
|
|
4
|
+
*/
|
|
5
|
+
import { createLogger } from "../utils/logger.js";
|
|
6
|
+
const logger = createLogger("feishu-api");
|
|
7
|
+
const FEISHU_BASE_URL = "https://open.feishu.cn/open-apis";
|
|
8
|
+
export function createFeishuApiClient(options) {
|
|
9
|
+
const { appId, appSecret } = options;
|
|
10
|
+
let tokenState = null;
|
|
11
|
+
let refreshPromise = null;
|
|
12
|
+
async function getToken() {
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
// Token still valid (refresh 5 min early)
|
|
15
|
+
if (tokenState && tokenState.expiresAt - now > 300_000) {
|
|
16
|
+
return tokenState.token;
|
|
17
|
+
}
|
|
18
|
+
// Deduplicate concurrent refresh calls
|
|
19
|
+
if (refreshPromise) {
|
|
20
|
+
return refreshPromise;
|
|
21
|
+
}
|
|
22
|
+
refreshPromise = refreshToken();
|
|
23
|
+
try {
|
|
24
|
+
return await refreshPromise;
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
refreshPromise = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function refreshToken() {
|
|
31
|
+
logger.info("Refreshing tenant_access_token...");
|
|
32
|
+
const response = await fetch(`${FEISHU_BASE_URL}/auth/v3/tenant_access_token/internal`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: { "Content-Type": "application/json" },
|
|
35
|
+
body: JSON.stringify({ app_id: appId, app_secret: appSecret }),
|
|
36
|
+
});
|
|
37
|
+
const data = (await response.json());
|
|
38
|
+
if (data.code !== 0) {
|
|
39
|
+
throw new Error(`Failed to get tenant_access_token: ${data.msg}`);
|
|
40
|
+
}
|
|
41
|
+
tokenState = {
|
|
42
|
+
token: data.tenant_access_token,
|
|
43
|
+
expiresAt: Date.now() + data.expire * 1000,
|
|
44
|
+
};
|
|
45
|
+
logger.info(`Token refreshed, expires in ${data.expire}s`);
|
|
46
|
+
return tokenState.token;
|
|
47
|
+
}
|
|
48
|
+
async function apiRequest(method, urlPath, body, retryCount = 0) {
|
|
49
|
+
const token = await getToken();
|
|
50
|
+
const response = await fetch(`${FEISHU_BASE_URL}${urlPath}`, {
|
|
51
|
+
method,
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
Authorization: `Bearer ${token}`,
|
|
55
|
+
},
|
|
56
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
57
|
+
});
|
|
58
|
+
const data = (await response.json());
|
|
59
|
+
// Token expired — refresh and retry once
|
|
60
|
+
if (data.code === 99991663 && retryCount < 1) {
|
|
61
|
+
tokenState = null;
|
|
62
|
+
return apiRequest(method, urlPath, body, retryCount + 1);
|
|
63
|
+
}
|
|
64
|
+
if (data.code !== 0) {
|
|
65
|
+
logger.error(`Feishu API error [${urlPath}]: ${data.code} - ${data.msg}`);
|
|
66
|
+
}
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
async sendMessage(chatId, body) {
|
|
71
|
+
return apiRequest("POST", "/im/v1/messages?receive_id_type=chat_id", {
|
|
72
|
+
receive_id: chatId,
|
|
73
|
+
msg_type: body.msg_type,
|
|
74
|
+
content: body.content,
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
async replyMessage(messageId, body) {
|
|
78
|
+
return apiRequest("POST", `/im/v1/messages/${messageId}/reply`, {
|
|
79
|
+
msg_type: body.msg_type,
|
|
80
|
+
content: body.content,
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
async updateMessage(messageId, content) {
|
|
84
|
+
return apiRequest("PATCH", `/im/v1/messages/${messageId}`, {
|
|
85
|
+
content,
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
async addReaction(messageId, emojiType) {
|
|
89
|
+
return apiRequest("POST", `/im/v1/messages/${messageId}/reactions`, {
|
|
90
|
+
reaction_type: { emoji_type: emojiType },
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
async deleteReaction(messageId, reactionId) {
|
|
94
|
+
return apiRequest("DELETE", `/im/v1/messages/${messageId}/reactions/${reactionId}`);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/feishu/api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feishu interactive card builder.
|
|
3
|
+
*
|
|
4
|
+
* Card size limit: 28KB
|
|
5
|
+
* Docs: https://open.feishu.cn/document/uAjLw4CM/ukzMukzMukzM/feishu-cards/card-components/content-components/rich-text
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildThinkingCard(): Record<string, unknown>;
|
|
8
|
+
export declare function buildResponseCard(text: string): Record<string, unknown>;
|
|
9
|
+
export declare function buildErrorCard(msg: string): Record<string, unknown>;
|
|
10
|
+
//# sourceMappingURL=card-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-builder.d.ts","sourceRoot":"","sources":["../../src/feishu/card-builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,wBAAgB,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoB3D;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0BvE;AAGD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoBnE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feishu interactive card builder.
|
|
3
|
+
*
|
|
4
|
+
* Card size limit: 28KB
|
|
5
|
+
* Docs: https://open.feishu.cn/document/uAjLw4CM/ukzMukzMukzM/feishu-cards/card-components/content-components/rich-text
|
|
6
|
+
*/
|
|
7
|
+
export function buildThinkingCard() {
|
|
8
|
+
return {
|
|
9
|
+
config: { wide_screen_mode: true },
|
|
10
|
+
header: {
|
|
11
|
+
title: {
|
|
12
|
+
tag: "plain_text",
|
|
13
|
+
content: "🤔 思考中...",
|
|
14
|
+
},
|
|
15
|
+
template: "blue",
|
|
16
|
+
},
|
|
17
|
+
elements: [
|
|
18
|
+
{
|
|
19
|
+
tag: "div",
|
|
20
|
+
text: {
|
|
21
|
+
tag: "lark_md",
|
|
22
|
+
content: "正在处理你的消息,请稍候...",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function buildResponseCard(text) {
|
|
29
|
+
// Feishu card limit is 28KB; truncate if needed
|
|
30
|
+
const truncated = text.length > 4000
|
|
31
|
+
? text.slice(0, 4000) + "\n\n...(内容过长,已截断)"
|
|
32
|
+
: text;
|
|
33
|
+
return {
|
|
34
|
+
config: { wide_screen_mode: true },
|
|
35
|
+
header: {
|
|
36
|
+
title: {
|
|
37
|
+
tag: "plain_text",
|
|
38
|
+
content: "✅ 回复",
|
|
39
|
+
},
|
|
40
|
+
template: "green",
|
|
41
|
+
},
|
|
42
|
+
elements: [
|
|
43
|
+
{
|
|
44
|
+
tag: "div",
|
|
45
|
+
text: {
|
|
46
|
+
tag: "lark_md",
|
|
47
|
+
content: truncated,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function buildErrorCard(msg) {
|
|
54
|
+
return {
|
|
55
|
+
config: { wide_screen_mode: true },
|
|
56
|
+
header: {
|
|
57
|
+
title: {
|
|
58
|
+
tag: "plain_text",
|
|
59
|
+
content: "❌ 出错了",
|
|
60
|
+
},
|
|
61
|
+
template: "red",
|
|
62
|
+
},
|
|
63
|
+
elements: [
|
|
64
|
+
{
|
|
65
|
+
tag: "div",
|
|
66
|
+
text: {
|
|
67
|
+
tag: "lark_md",
|
|
68
|
+
content: msg || "处理请求时发生错误,请稍后重试。",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=card-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-builder.js","sourceRoot":"","sources":["../../src/feishu/card-builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,MAAM,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE;QAClC,MAAM,EAAE;YACN,KAAK,EAAE;gBACL,GAAG,EAAE,YAAY;gBACjB,OAAO,EAAE,WAAW;aACrB;YACD,QAAQ,EAAE,MAAM;SACjB;QACD,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,KAAK;gBACV,IAAI,EAAE;oBACJ,GAAG,EAAE,SAAS;oBACd,OAAO,EAAE,iBAAiB;iBAC3B;aACF;SACF;KACF,CAAA;AACH,CAAC;AAGD,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,gDAAgD;IAChD,MAAM,SAAS,GACb,IAAI,CAAC,MAAM,GAAG,IAAI;QAChB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,mBAAmB;QAC3C,CAAC,CAAC,IAAI,CAAA;IAEV,OAAO;QACL,MAAM,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE;QAClC,MAAM,EAAE;YACN,KAAK,EAAE;gBACL,GAAG,EAAE,YAAY;gBACjB,OAAO,EAAE,MAAM;aAChB;YACD,QAAQ,EAAE,OAAO;SAClB;QACD,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,KAAK;gBACV,IAAI,EAAE;oBACJ,GAAG,EAAE,SAAS;oBACd,OAAO,EAAE,SAAS;iBACnB;aACF;SACF;KACF,CAAA;AACH,CAAC;AAGD,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO;QACL,MAAM,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE;QAClC,MAAM,EAAE;YACN,KAAK,EAAE;gBACL,GAAG,EAAE,YAAY;gBACjB,OAAO,EAAE,OAAO;aACjB;YACD,QAAQ,EAAE,KAAK;SAChB;QACD,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,KAAK;gBACV,IAAI,EAAE;oBACJ,GAAG,EAAE,SAAS;oBACd,OAAO,EAAE,GAAG,IAAI,kBAAkB;iBACnC;aACF;SACF;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface CardKitSchema {
|
|
2
|
+
schema: "2.0";
|
|
3
|
+
config: {
|
|
4
|
+
streaming_mode: boolean;
|
|
5
|
+
summary: {
|
|
6
|
+
content: string;
|
|
7
|
+
};
|
|
8
|
+
streaming_config?: {
|
|
9
|
+
print_frequency_ms: {
|
|
10
|
+
default: number;
|
|
11
|
+
};
|
|
12
|
+
print_step: {
|
|
13
|
+
default: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
body: {
|
|
18
|
+
elements: CardElement[];
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface CardElement {
|
|
22
|
+
tag: string;
|
|
23
|
+
content: string;
|
|
24
|
+
element_id: string;
|
|
25
|
+
}
|
|
26
|
+
export declare class CardKitError extends Error {
|
|
27
|
+
code: number;
|
|
28
|
+
constructor(code: number, message: string);
|
|
29
|
+
}
|
|
30
|
+
export declare class CardKitClient {
|
|
31
|
+
private readonly appId;
|
|
32
|
+
private readonly appSecret;
|
|
33
|
+
private readonly apiBase;
|
|
34
|
+
private tokenState;
|
|
35
|
+
private refreshPromise;
|
|
36
|
+
constructor(options: {
|
|
37
|
+
appId: string;
|
|
38
|
+
appSecret: string;
|
|
39
|
+
apiBase?: string;
|
|
40
|
+
});
|
|
41
|
+
createCard(cardJson: CardKitSchema): Promise<string>;
|
|
42
|
+
updateElement(cardId: string, elementId: string, content: string, sequence: number): Promise<void>;
|
|
43
|
+
closeStreaming(cardId: string, summary: string, sequence: number): Promise<void>;
|
|
44
|
+
private getToken;
|
|
45
|
+
private refreshToken;
|
|
46
|
+
private apiRequest;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=cardkit-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cardkit-client.d.ts","sourceRoot":"","sources":["../../src/feishu/cardkit-client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,KAAK,CAAA;IACb,MAAM,EAAE;QACN,cAAc,EAAE,OAAO,CAAA;QACvB,OAAO,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;QAC5B,gBAAgB,CAAC,EAAE;YACjB,kBAAkB,EAAE;gBAAE,OAAO,EAAE,MAAM,CAAA;aAAE,CAAA;YACvC,UAAU,EAAE;gBAAE,OAAO,EAAE,MAAM,CAAA;aAAE,CAAA;SAChC,CAAA;KACF,CAAA;IACD,IAAI,EAAE;QACJ,QAAQ,EAAE,WAAW,EAAE,CAAA;KACxB,CAAA;CACF;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,IAAI,EAAE,MAAM,CAAA;gBAEA,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAeD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAA+B;gBAEzC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAMrE,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAapD,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAQV,cAAc,CAClB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;YAUF,QAAQ;YAkBR,YAAY;YA4BZ,UAAU;CA8BzB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export class CardKitError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "CardKitError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const DEFAULT_API_BASE = "https://open.feishu.cn/open-apis";
|
|
10
|
+
export class CardKitClient {
|
|
11
|
+
appId;
|
|
12
|
+
appSecret;
|
|
13
|
+
apiBase;
|
|
14
|
+
tokenState = null;
|
|
15
|
+
refreshPromise = null;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.appId = options.appId;
|
|
18
|
+
this.appSecret = options.appSecret;
|
|
19
|
+
this.apiBase = options.apiBase ?? DEFAULT_API_BASE;
|
|
20
|
+
}
|
|
21
|
+
async createCard(cardJson) {
|
|
22
|
+
const res = await this.apiRequest("POST", "/cardkit/v1/cards", {
|
|
23
|
+
type: "card_json",
|
|
24
|
+
data: JSON.stringify(cardJson),
|
|
25
|
+
});
|
|
26
|
+
const cardId = res.data?.card_id;
|
|
27
|
+
if (typeof cardId !== "string") {
|
|
28
|
+
throw new CardKitError(res.code, "Missing card_id in response");
|
|
29
|
+
}
|
|
30
|
+
return cardId;
|
|
31
|
+
}
|
|
32
|
+
async updateElement(cardId, elementId, content, sequence) {
|
|
33
|
+
await this.apiRequest("PUT", `/cardkit/v1/cards/${cardId}/elements/${elementId}/content`, { content, sequence, uuid: `s_${cardId}_${sequence}` });
|
|
34
|
+
}
|
|
35
|
+
async closeStreaming(cardId, summary, sequence) {
|
|
36
|
+
await this.apiRequest("PATCH", `/cardkit/v1/cards/${cardId}/settings`, {
|
|
37
|
+
settings: JSON.stringify({
|
|
38
|
+
config: { streaming_mode: false, summary: { content: summary } },
|
|
39
|
+
}),
|
|
40
|
+
sequence,
|
|
41
|
+
uuid: `c_${cardId}_${sequence}`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async getToken() {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
if (this.tokenState && this.tokenState.expiresAt - now > 300_000) {
|
|
47
|
+
return this.tokenState.token;
|
|
48
|
+
}
|
|
49
|
+
if (this.refreshPromise) {
|
|
50
|
+
return this.refreshPromise;
|
|
51
|
+
}
|
|
52
|
+
this.refreshPromise = this.refreshToken();
|
|
53
|
+
try {
|
|
54
|
+
return await this.refreshPromise;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
this.refreshPromise = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async refreshToken() {
|
|
61
|
+
const res = await fetch(`${this.apiBase}/auth/v3/tenant_access_token/internal`, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: { "Content-Type": "application/json" },
|
|
64
|
+
body: JSON.stringify({ app_id: this.appId, app_secret: this.appSecret }),
|
|
65
|
+
});
|
|
66
|
+
const data = (await res.json());
|
|
67
|
+
if (data.code !== 0 || !data.tenant_access_token) {
|
|
68
|
+
throw new CardKitError(data.code, `Token error: ${data.msg}`);
|
|
69
|
+
}
|
|
70
|
+
this.tokenState = {
|
|
71
|
+
token: data.tenant_access_token,
|
|
72
|
+
expiresAt: Date.now() + (data.expire ?? 7200) * 1000,
|
|
73
|
+
};
|
|
74
|
+
return this.tokenState.token;
|
|
75
|
+
}
|
|
76
|
+
async apiRequest(method, urlPath, body, retryCount = 0) {
|
|
77
|
+
const token = await this.getToken();
|
|
78
|
+
const res = await fetch(`${this.apiBase}${urlPath}`, {
|
|
79
|
+
method,
|
|
80
|
+
headers: {
|
|
81
|
+
"Content-Type": "application/json",
|
|
82
|
+
Authorization: `Bearer ${token}`,
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify(body),
|
|
85
|
+
});
|
|
86
|
+
const data = (await res.json());
|
|
87
|
+
if (data.code === 99991663 && retryCount < 1) {
|
|
88
|
+
this.tokenState = null;
|
|
89
|
+
return this.apiRequest(method, urlPath, body, retryCount + 1);
|
|
90
|
+
}
|
|
91
|
+
if (data.code !== 0) {
|
|
92
|
+
throw new CardKitError(data.code, data.msg);
|
|
93
|
+
}
|
|
94
|
+
return data;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=cardkit-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cardkit-client.js","sourceRoot":"","sources":["../../src/feishu/cardkit-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite-backed message deduplication.
|
|
3
|
+
* Caches event_id with a configurable TTL (default 60s).
|
|
4
|
+
* Prevents processing the same Feishu event twice, surviving restarts.
|
|
5
|
+
*/
|
|
6
|
+
import { type Database } from "bun:sqlite";
|
|
7
|
+
interface MessageDedupOptions {
|
|
8
|
+
db: Database;
|
|
9
|
+
ttlMs?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class MessageDedup {
|
|
12
|
+
private readonly db;
|
|
13
|
+
private readonly ttlMs;
|
|
14
|
+
private readonly insertStmt;
|
|
15
|
+
private readonly checkStmt;
|
|
16
|
+
private readonly cleanupStmt;
|
|
17
|
+
private cleanupTimer;
|
|
18
|
+
constructor(options: MessageDedupOptions);
|
|
19
|
+
/**
|
|
20
|
+
* Returns true if this event_id has been seen within the TTL window.
|
|
21
|
+
* If not seen, registers it and returns false.
|
|
22
|
+
*/
|
|
23
|
+
isDuplicate(eventId: string): boolean;
|
|
24
|
+
private evictExpired;
|
|
25
|
+
close(): void;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=message-dedup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-dedup.d.ts","sourceRoot":"","sources":["../../src/feishu/message-dedup.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,QAAQ,EAAkB,MAAM,YAAY,CAAA;AAK1D,UAAU,mBAAmB;IAC3B,EAAE,EAAE,QAAQ,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAU;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAW;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAW;IACvC,OAAO,CAAC,YAAY,CAA8C;gBAEtD,OAAO,EAAE,mBAAmB;IA8BxC;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAcrC,OAAO,CAAC,YAAY;IAQpB,KAAK,IAAI,IAAI;CAMd"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite-backed message deduplication.
|
|
3
|
+
* Caches event_id with a configurable TTL (default 60s).
|
|
4
|
+
* Prevents processing the same Feishu event twice, surviving restarts.
|
|
5
|
+
*/
|
|
6
|
+
import { createLogger } from "../utils/logger.js";
|
|
7
|
+
const logger = createLogger("message-dedup");
|
|
8
|
+
export class MessageDedup {
|
|
9
|
+
db;
|
|
10
|
+
ttlMs;
|
|
11
|
+
insertStmt;
|
|
12
|
+
checkStmt;
|
|
13
|
+
cleanupStmt;
|
|
14
|
+
cleanupTimer = null;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.db = options.db;
|
|
17
|
+
this.ttlMs = options.ttlMs ?? 60_000;
|
|
18
|
+
this.db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS message_dedup (
|
|
20
|
+
event_id TEXT PRIMARY KEY,
|
|
21
|
+
created_at INTEGER NOT NULL
|
|
22
|
+
)
|
|
23
|
+
`);
|
|
24
|
+
this.checkStmt = this.db.prepare("SELECT event_id FROM message_dedup WHERE event_id = ?");
|
|
25
|
+
this.insertStmt = this.db.prepare("INSERT OR IGNORE INTO message_dedup (event_id, created_at) VALUES (?, ?)");
|
|
26
|
+
this.cleanupStmt = this.db.prepare("DELETE FROM message_dedup WHERE created_at < ?");
|
|
27
|
+
this.cleanupTimer = setInterval(() => this.evictExpired(), 30_000);
|
|
28
|
+
logger.info(`MessageDedup initialized (TTL: ${this.ttlMs}ms)`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns true if this event_id has been seen within the TTL window.
|
|
32
|
+
* If not seen, registers it and returns false.
|
|
33
|
+
*/
|
|
34
|
+
isDuplicate(eventId) {
|
|
35
|
+
this.evictExpired();
|
|
36
|
+
const existing = this.checkStmt.get(eventId);
|
|
37
|
+
if (existing) {
|
|
38
|
+
logger.debug(`Duplicate event: ${eventId}`);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
this.insertStmt.run(eventId, Date.now());
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
evictExpired() {
|
|
45
|
+
const cutoff = Date.now() - this.ttlMs;
|
|
46
|
+
const result = this.cleanupStmt.run(cutoff);
|
|
47
|
+
if (result.changes > 0) {
|
|
48
|
+
logger.debug(`Evicted ${result.changes} expired dedup entries`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
close() {
|
|
52
|
+
if (this.cleanupTimer) {
|
|
53
|
+
clearInterval(this.cleanupTimer);
|
|
54
|
+
this.cleanupTimer = null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=message-dedup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-dedup.js","sourceRoot":"","sources":["../../src/feishu/message-dedup.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,MAAM,MAAM,GAAG,YAAY,CAAC,eAAe,CAAC,CAAA;AAO5C,MAAM,OAAO,YAAY;IACN,EAAE,CAAU;IACZ,KAAK,CAAQ;IACb,UAAU,CAAW;IACrB,SAAS,CAAW;IACpB,WAAW,CAAW;IAC/B,YAAY,GAA0C,IAAI,CAAA;IAElE,YAAY,OAA4B;QACtC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,CAAA;QACpB,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAAA;QAGpC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;;;;;KAKZ,CAAC,CAAA;QAEF,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC9B,uDAAuD,CACxD,CAAA;QAED,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC/B,0EAA0E,CAC3E,CAAA;QAED,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAChC,gDAAgD,CACjD,CAAA;QAGD,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,MAAM,CAAC,CAAA;QAElE,MAAM,CAAC,IAAI,CAAC,kCAAkC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAA;IAChE,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,OAAe;QAEzB,IAAI,CAAC,YAAY,EAAE,CAAA;QAEnB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAC5C,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAA;YAC3C,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;QACxC,OAAO,KAAK,CAAA;IACd,CAAC;IAEO,YAAY;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QACtC,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAC3C,IAAI,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,KAAK,CAAC,WAAW,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAA;QACjE,CAAC;IACH,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;YAChC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;QAC1B,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feishu Webhook server.
|
|
3
|
+
* Receives Feishu event subscription callbacks and card action callbacks.
|
|
4
|
+
*/
|
|
5
|
+
import { MessageDedup } from "./message-dedup.js";
|
|
6
|
+
import type { FeishuMessageEvent, FeishuCardAction } from "../types.js";
|
|
7
|
+
interface WebhookServerOptions {
|
|
8
|
+
port: number;
|
|
9
|
+
verificationToken: string;
|
|
10
|
+
onMessage: (event: FeishuMessageEvent) => Promise<void>;
|
|
11
|
+
onCardAction: (action: FeishuCardAction) => Promise<void>;
|
|
12
|
+
dedup: MessageDedup;
|
|
13
|
+
}
|
|
14
|
+
export interface WebhookServer {
|
|
15
|
+
port: number;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare function createFeishuGateway(options: WebhookServerOptions): Promise<WebhookServer>;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=webhook-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-server.d.ts","sourceRoot":"","sources":["../../src/feishu/webhook-server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAIvE,UAAU,oBAAoB;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,iBAAiB,EAAE,MAAM,CAAA;IACzB,SAAS,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACvD,YAAY,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACzD,KAAK,EAAE,YAAY,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,CAAC,CAsHxB"}
|