@axiom-lattice/gateway 2.1.52 → 2.1.54
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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +22 -0
- package/dist/index.js +717 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +713 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
- package/src/__tests__/channel-installations.test.ts +199 -0
- package/src/channels/__tests__/routes.test.ts +71 -0
- package/src/channels/lark/README.md +187 -0
- package/src/channels/lark/__tests__/aggregator.test.ts +23 -0
- package/src/channels/lark/__tests__/controller.test.ts +270 -0
- package/src/channels/lark/__tests__/mapping-service.test.ts +118 -0
- package/src/channels/lark/__tests__/parser.test.ts +72 -0
- package/src/channels/lark/__tests__/sender.test.ts +37 -0
- package/src/channels/lark/__tests__/verification.test.ts +157 -0
- package/src/channels/lark/aggregator.ts +16 -0
- package/src/channels/lark/config.ts +44 -0
- package/src/channels/lark/controller.ts +189 -0
- package/src/channels/lark/mapping-service.ts +138 -0
- package/src/channels/lark/parser.ts +68 -0
- package/src/channels/lark/routes.ts +121 -0
- package/src/channels/lark/runner.ts +37 -0
- package/src/channels/lark/sender.ts +58 -0
- package/src/channels/lark/types.ts +33 -0
- package/src/channels/lark/verification.ts +67 -0
- package/src/channels/routes.ts +25 -0
- package/src/controllers/channel-installations.ts +354 -0
- package/src/controllers/threads.ts +8 -6
- package/src/routes/channel-installations.ts +33 -0
- package/src/routes/index.ts +6 -0
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/index.ts
|
|
2
9
|
import fastify from "fastify";
|
|
3
10
|
import cors from "@fastify/cors";
|
|
@@ -591,11 +598,13 @@ async function getThreadList(request, reply) {
|
|
|
591
598
|
const tenantId = getTenantId2(request);
|
|
592
599
|
const { assistantId } = request.params;
|
|
593
600
|
const metadataFilter = {};
|
|
594
|
-
|
|
595
|
-
|
|
601
|
+
const workspaceId = request.headers["x-workspace-id"];
|
|
602
|
+
const projectId = request.headers["x-project-id"];
|
|
603
|
+
if (workspaceId) {
|
|
604
|
+
metadataFilter.workspaceId = workspaceId;
|
|
596
605
|
}
|
|
597
|
-
if (
|
|
598
|
-
metadataFilter.projectId =
|
|
606
|
+
if (projectId) {
|
|
607
|
+
metadataFilter.projectId = projectId;
|
|
599
608
|
}
|
|
600
609
|
const storeLattice = getStoreLattice2("default", "thread");
|
|
601
610
|
const threadStore = storeLattice.store;
|
|
@@ -4878,6 +4887,698 @@ function registerAuthRoutes(app2, config) {
|
|
|
4878
4887
|
);
|
|
4879
4888
|
}
|
|
4880
4889
|
|
|
4890
|
+
// src/channels/lark/routes.ts
|
|
4891
|
+
import { getStoreLattice as getStoreLattice12 } from "@axiom-lattice/core";
|
|
4892
|
+
import {
|
|
4893
|
+
ChannelIdentityMappingStore,
|
|
4894
|
+
PostgreSQLChannelInstallationStore
|
|
4895
|
+
} from "@axiom-lattice/pg-stores";
|
|
4896
|
+
|
|
4897
|
+
// src/channels/lark/parser.ts
|
|
4898
|
+
function parseLarkMessageEvent(payload) {
|
|
4899
|
+
const raw = payload;
|
|
4900
|
+
if (raw.header?.event_type !== "im.message.receive_v1") {
|
|
4901
|
+
return null;
|
|
4902
|
+
}
|
|
4903
|
+
const message = raw.event?.message;
|
|
4904
|
+
const openId = raw.event?.sender?.sender_id?.open_id;
|
|
4905
|
+
if (!message || !openId || message.message_type !== "text") {
|
|
4906
|
+
return null;
|
|
4907
|
+
}
|
|
4908
|
+
const parsedContent = parseLarkTextContent(message.content);
|
|
4909
|
+
if (!message.message_id || !message.chat_id || !parsedContent) {
|
|
4910
|
+
return null;
|
|
4911
|
+
}
|
|
4912
|
+
return {
|
|
4913
|
+
messageId: message.message_id,
|
|
4914
|
+
openId,
|
|
4915
|
+
chatId: message.chat_id,
|
|
4916
|
+
chatType: normalizeChatType(message.chat_type),
|
|
4917
|
+
text: parsedContent
|
|
4918
|
+
};
|
|
4919
|
+
}
|
|
4920
|
+
function parseLarkTextContent(content) {
|
|
4921
|
+
if (!content) {
|
|
4922
|
+
return null;
|
|
4923
|
+
}
|
|
4924
|
+
try {
|
|
4925
|
+
const parsed = JSON.parse(content);
|
|
4926
|
+
return typeof parsed.text === "string" ? parsed.text : null;
|
|
4927
|
+
} catch {
|
|
4928
|
+
return null;
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
function normalizeChatType(chatType) {
|
|
4932
|
+
return chatType === "p2p" ? "direct" : "group";
|
|
4933
|
+
}
|
|
4934
|
+
|
|
4935
|
+
// src/channels/lark/controller.ts
|
|
4936
|
+
function createLarkEventHandler(dependencies) {
|
|
4937
|
+
return async function handleLarkEvent2(request, reply) {
|
|
4938
|
+
const installationId = request.params?.installationId;
|
|
4939
|
+
if (!installationId) {
|
|
4940
|
+
reply.status(400).send({ success: false, message: "Missing installationId" });
|
|
4941
|
+
return;
|
|
4942
|
+
}
|
|
4943
|
+
const config = await dependencies.getInstallationConfig(installationId);
|
|
4944
|
+
if (!config) {
|
|
4945
|
+
reply.status(404).send({ success: false, message: "Lark installation not found" });
|
|
4946
|
+
return;
|
|
4947
|
+
}
|
|
4948
|
+
const body = dependencies.parseRequestBody(
|
|
4949
|
+
request.body,
|
|
4950
|
+
config.encryptKey
|
|
4951
|
+
);
|
|
4952
|
+
if (!dependencies.verifyParsedBody(body, config)) {
|
|
4953
|
+
reply.status(401).send({ success: false, message: "Invalid Lark request" });
|
|
4954
|
+
return;
|
|
4955
|
+
}
|
|
4956
|
+
if (body.type === "url_verification" && body.challenge) {
|
|
4957
|
+
reply.status(200).send({ challenge: body.challenge });
|
|
4958
|
+
return;
|
|
4959
|
+
}
|
|
4960
|
+
const parsed = dependencies.parseEvent(request.body);
|
|
4961
|
+
if (!parsed) {
|
|
4962
|
+
reply.status(200).send({ success: true, ignored: true });
|
|
4963
|
+
return;
|
|
4964
|
+
}
|
|
4965
|
+
const receipt = await dependencies.claimInboundReceipt({
|
|
4966
|
+
channel: "lark",
|
|
4967
|
+
channelAppId: config.appId,
|
|
4968
|
+
externalMessageId: parsed.messageId,
|
|
4969
|
+
tenantId: config.tenantId
|
|
4970
|
+
});
|
|
4971
|
+
if (!receipt.accepted) {
|
|
4972
|
+
reply.status(200).send(
|
|
4973
|
+
receipt.status === "processing" ? { success: true, processing: true } : { success: true, duplicate: true }
|
|
4974
|
+
);
|
|
4975
|
+
return;
|
|
4976
|
+
}
|
|
4977
|
+
try {
|
|
4978
|
+
const { threadId } = await dependencies.resolveThread({
|
|
4979
|
+
channel: "lark",
|
|
4980
|
+
channelAppId: config.appId,
|
|
4981
|
+
tenantId: config.tenantId,
|
|
4982
|
+
assistantId: config.assistantId,
|
|
4983
|
+
mappingMode: config.mappingMode,
|
|
4984
|
+
openId: parsed.openId,
|
|
4985
|
+
chatId: parsed.chatId,
|
|
4986
|
+
chatType: parsed.chatType,
|
|
4987
|
+
messageId: parsed.messageId,
|
|
4988
|
+
workspaceId: config.workspaceId,
|
|
4989
|
+
projectId: config.projectId
|
|
4990
|
+
});
|
|
4991
|
+
const text = await dependencies.runAgentAndCollectText({
|
|
4992
|
+
tenantId: config.tenantId,
|
|
4993
|
+
assistantId: config.assistantId,
|
|
4994
|
+
threadId,
|
|
4995
|
+
text: parsed.text,
|
|
4996
|
+
workspaceId: config.workspaceId,
|
|
4997
|
+
projectId: config.projectId
|
|
4998
|
+
});
|
|
4999
|
+
await dependencies.sendTextReply({
|
|
5000
|
+
chatId: parsed.chatId,
|
|
5001
|
+
text,
|
|
5002
|
+
config
|
|
5003
|
+
});
|
|
5004
|
+
await dependencies.markInboundReceiptCompleted({
|
|
5005
|
+
channel: "lark",
|
|
5006
|
+
channelAppId: config.appId,
|
|
5007
|
+
externalMessageId: parsed.messageId,
|
|
5008
|
+
tenantId: config.tenantId,
|
|
5009
|
+
threadId
|
|
5010
|
+
});
|
|
5011
|
+
reply.status(200).send({ success: true, threadId });
|
|
5012
|
+
} catch (error) {
|
|
5013
|
+
await dependencies.markInboundReceiptFailed({
|
|
5014
|
+
channel: "lark",
|
|
5015
|
+
channelAppId: config.appId,
|
|
5016
|
+
externalMessageId: parsed.messageId,
|
|
5017
|
+
tenantId: config.tenantId
|
|
5018
|
+
});
|
|
5019
|
+
throw error;
|
|
5020
|
+
}
|
|
5021
|
+
};
|
|
5022
|
+
}
|
|
5023
|
+
var handleLarkEvent = createLarkEventHandler({
|
|
5024
|
+
getInstallationConfig: async () => null,
|
|
5025
|
+
parseRequestBody: (body) => body || {},
|
|
5026
|
+
verifyParsedBody: () => true,
|
|
5027
|
+
parseEvent: parseLarkMessageEvent,
|
|
5028
|
+
claimInboundReceipt: async () => ({ accepted: true, status: "processing" }),
|
|
5029
|
+
markInboundReceiptCompleted: async () => void 0,
|
|
5030
|
+
markInboundReceiptFailed: async () => void 0,
|
|
5031
|
+
resolveThread: async () => ({ threadId: "" }),
|
|
5032
|
+
runAgentAndCollectText: async () => "",
|
|
5033
|
+
sendTextReply: async () => void 0
|
|
5034
|
+
});
|
|
5035
|
+
|
|
5036
|
+
// src/channels/lark/config.ts
|
|
5037
|
+
function loadLarkIngressConfig() {
|
|
5038
|
+
return {
|
|
5039
|
+
enabled: process.env.LARK_ENABLED !== "false",
|
|
5040
|
+
appId: process.env.LARK_APP_ID || "",
|
|
5041
|
+
appSecret: process.env.LARK_APP_SECRET || "",
|
|
5042
|
+
verificationToken: process.env.LARK_VERIFICATION_TOKEN,
|
|
5043
|
+
encryptKey: process.env.LARK_ENCRYPT_KEY,
|
|
5044
|
+
tenantId: process.env.LARK_TENANT_ID || "default",
|
|
5045
|
+
assistantId: process.env.LARK_ASSISTANT_ID || "default_agent",
|
|
5046
|
+
workspaceId: process.env.LARK_WORKSPACE_ID,
|
|
5047
|
+
projectId: process.env.LARK_PROJECT_ID,
|
|
5048
|
+
mappingMode: process.env.LARK_MAPPING_MODE || "hybrid"
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
function isLarkIngressEnabled(config) {
|
|
5052
|
+
return config.enabled;
|
|
5053
|
+
}
|
|
5054
|
+
|
|
5055
|
+
// src/channels/lark/mapping-service.ts
|
|
5056
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
5057
|
+
function createChannelThreadMappingService(deps) {
|
|
5058
|
+
return {
|
|
5059
|
+
async getOrCreateThread(input) {
|
|
5060
|
+
const externalSubjectKey = buildExternalSubjectKey(input);
|
|
5061
|
+
const existing = await deps.mappingStore.getMappingBySubject({
|
|
5062
|
+
channel: input.channel,
|
|
5063
|
+
channelAppId: input.channelAppId,
|
|
5064
|
+
tenantId: input.tenantId,
|
|
5065
|
+
assistantId: input.assistantId,
|
|
5066
|
+
externalSubjectKey
|
|
5067
|
+
});
|
|
5068
|
+
if (existing) {
|
|
5069
|
+
return { threadId: existing.threadId };
|
|
5070
|
+
}
|
|
5071
|
+
const threadId = (deps.uuid || randomUUID6)();
|
|
5072
|
+
await deps.threadStore.createThread(input.tenantId, input.assistantId, threadId, {
|
|
5073
|
+
metadata: {
|
|
5074
|
+
tenantId: input.tenantId,
|
|
5075
|
+
workspaceId: input.workspaceId,
|
|
5076
|
+
projectId: input.projectId,
|
|
5077
|
+
channel: input.channel,
|
|
5078
|
+
larkChatId: input.chatId,
|
|
5079
|
+
larkOpenId: input.openId
|
|
5080
|
+
}
|
|
5081
|
+
});
|
|
5082
|
+
try {
|
|
5083
|
+
await deps.mappingStore.createMapping({
|
|
5084
|
+
channel: input.channel,
|
|
5085
|
+
channelAppId: input.channelAppId,
|
|
5086
|
+
tenantId: input.tenantId,
|
|
5087
|
+
assistantId: input.assistantId,
|
|
5088
|
+
mappingMode: input.mappingMode,
|
|
5089
|
+
externalSubjectType: resolveSubjectType(input.mappingMode, input.chatType) === "user" ? "user" : "chat",
|
|
5090
|
+
externalSubjectKey,
|
|
5091
|
+
larkOpenId: input.openId,
|
|
5092
|
+
larkChatId: input.chatId,
|
|
5093
|
+
larkMessageId: input.messageId,
|
|
5094
|
+
threadId
|
|
5095
|
+
});
|
|
5096
|
+
} catch (error) {
|
|
5097
|
+
if (!isUniqueViolation(error)) {
|
|
5098
|
+
throw error;
|
|
5099
|
+
}
|
|
5100
|
+
const canonical = await deps.mappingStore.getMappingBySubject({
|
|
5101
|
+
channel: input.channel,
|
|
5102
|
+
channelAppId: input.channelAppId,
|
|
5103
|
+
tenantId: input.tenantId,
|
|
5104
|
+
assistantId: input.assistantId,
|
|
5105
|
+
externalSubjectKey
|
|
5106
|
+
});
|
|
5107
|
+
if (!canonical) {
|
|
5108
|
+
throw error;
|
|
5109
|
+
}
|
|
5110
|
+
await deps.threadStore.deleteThread(input.tenantId, threadId);
|
|
5111
|
+
return { threadId: canonical.threadId };
|
|
5112
|
+
}
|
|
5113
|
+
return { threadId };
|
|
5114
|
+
}
|
|
5115
|
+
};
|
|
5116
|
+
}
|
|
5117
|
+
function isUniqueViolation(error) {
|
|
5118
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
5119
|
+
}
|
|
5120
|
+
function buildExternalSubjectKey(input) {
|
|
5121
|
+
const subjectType = resolveSubjectType(input.mappingMode, input.chatType);
|
|
5122
|
+
const subjectValue = subjectType === "user" ? input.openId : input.chatId;
|
|
5123
|
+
return [
|
|
5124
|
+
input.channel,
|
|
5125
|
+
input.channelAppId,
|
|
5126
|
+
`tenant:${input.tenantId}`,
|
|
5127
|
+
`assistant:${input.assistantId}`,
|
|
5128
|
+
`${subjectType}:${subjectValue}`
|
|
5129
|
+
].join(":");
|
|
5130
|
+
}
|
|
5131
|
+
function resolveSubjectType(mappingMode, chatType) {
|
|
5132
|
+
if (mappingMode === "user") {
|
|
5133
|
+
return "user";
|
|
5134
|
+
}
|
|
5135
|
+
if (mappingMode === "group") {
|
|
5136
|
+
return "chat";
|
|
5137
|
+
}
|
|
5138
|
+
return chatType === "direct" ? "user" : "chat";
|
|
5139
|
+
}
|
|
5140
|
+
|
|
5141
|
+
// src/channels/lark/runner.ts
|
|
5142
|
+
import { agentInstanceManager as agentInstanceManager5 } from "@axiom-lattice/core";
|
|
5143
|
+
import { MessageChunkTypes as MessageChunkTypes3 } from "@axiom-lattice/protocols";
|
|
5144
|
+
|
|
5145
|
+
// src/channels/lark/aggregator.ts
|
|
5146
|
+
import { MessageChunkTypes as MessageChunkTypes2 } from "@axiom-lattice/protocols";
|
|
5147
|
+
function aggregateLarkReply(messageId, chunks) {
|
|
5148
|
+
return chunks.filter(
|
|
5149
|
+
(chunk) => chunk.type === MessageChunkTypes2.AI && chunk.data.id === messageId
|
|
5150
|
+
).map((chunk) => chunk.data.content || "").join("").trim();
|
|
5151
|
+
}
|
|
5152
|
+
|
|
5153
|
+
// src/channels/lark/runner.ts
|
|
5154
|
+
async function runAgentAndCollectLarkReply(input) {
|
|
5155
|
+
const agent = agentInstanceManager5.getAgent({
|
|
5156
|
+
tenant_id: input.tenantId,
|
|
5157
|
+
assistant_id: input.assistantId,
|
|
5158
|
+
thread_id: input.threadId,
|
|
5159
|
+
workspace_id: input.workspaceId,
|
|
5160
|
+
project_id: input.projectId
|
|
5161
|
+
});
|
|
5162
|
+
const result = await agent.addMessage({
|
|
5163
|
+
input: {
|
|
5164
|
+
message: input.text
|
|
5165
|
+
}
|
|
5166
|
+
});
|
|
5167
|
+
const chunks = [];
|
|
5168
|
+
const stream = agent.chunkStream(result.messageId, [
|
|
5169
|
+
MessageChunkTypes3.MESSAGE_COMPLETED
|
|
5170
|
+
]);
|
|
5171
|
+
for await (const chunk of stream) {
|
|
5172
|
+
chunks.push(chunk);
|
|
5173
|
+
}
|
|
5174
|
+
return aggregateLarkReply(result.messageId, chunks);
|
|
5175
|
+
}
|
|
5176
|
+
|
|
5177
|
+
// src/channels/lark/sender.ts
|
|
5178
|
+
function createLarkSender(config, client = createDefaultLarkClient(config)) {
|
|
5179
|
+
return {
|
|
5180
|
+
async sendTextReply(input) {
|
|
5181
|
+
const response = await client.im.v1.message.create({
|
|
5182
|
+
params: {
|
|
5183
|
+
receive_id_type: "chat_id"
|
|
5184
|
+
},
|
|
5185
|
+
data: {
|
|
5186
|
+
receive_id: input.chatId,
|
|
5187
|
+
msg_type: "text",
|
|
5188
|
+
content: JSON.stringify({ text: input.text })
|
|
5189
|
+
}
|
|
5190
|
+
});
|
|
5191
|
+
if (response.code && response.code !== 0) {
|
|
5192
|
+
throw new Error("Failed to send Lark reply");
|
|
5193
|
+
}
|
|
5194
|
+
}
|
|
5195
|
+
};
|
|
5196
|
+
}
|
|
5197
|
+
function createDefaultLarkClient(config) {
|
|
5198
|
+
const Lark = __require("@larksuiteoapi/node-sdk");
|
|
5199
|
+
return new Lark.Client({
|
|
5200
|
+
appId: config.appId,
|
|
5201
|
+
appSecret: config.appSecret
|
|
5202
|
+
});
|
|
5203
|
+
}
|
|
5204
|
+
|
|
5205
|
+
// src/channels/lark/verification.ts
|
|
5206
|
+
import crypto2 from "crypto";
|
|
5207
|
+
function parseLarkRequestBody(body, encryptKey) {
|
|
5208
|
+
const parsed = body || {};
|
|
5209
|
+
if (encryptKey && typeof parsed.encrypt === "string") {
|
|
5210
|
+
return decryptLarkPayload(encryptKey, parsed.encrypt);
|
|
5211
|
+
}
|
|
5212
|
+
return parsed;
|
|
5213
|
+
}
|
|
5214
|
+
function decryptLarkPayload(encryptKey, encryptedPayload) {
|
|
5215
|
+
const key = crypto2.createHash("sha256").update(encryptKey).digest();
|
|
5216
|
+
const buffer = Buffer.from(encryptedPayload, "base64");
|
|
5217
|
+
const iv = buffer.subarray(0, 16);
|
|
5218
|
+
const ciphertext = buffer.subarray(16);
|
|
5219
|
+
const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
|
|
5220
|
+
const plaintext = Buffer.concat([
|
|
5221
|
+
decipher.update(ciphertext),
|
|
5222
|
+
decipher.final()
|
|
5223
|
+
]).toString("utf8");
|
|
5224
|
+
return JSON.parse(plaintext);
|
|
5225
|
+
}
|
|
5226
|
+
function createLarkRequestVerifier(config) {
|
|
5227
|
+
return function verifyRequest(request) {
|
|
5228
|
+
const body = parseLarkRequestBody(request.body, config.encryptKey);
|
|
5229
|
+
return verifyLarkParsedBody(body, config);
|
|
5230
|
+
};
|
|
5231
|
+
}
|
|
5232
|
+
function verifyLarkParsedBody(body, config) {
|
|
5233
|
+
if (!config.verificationToken) {
|
|
5234
|
+
return true;
|
|
5235
|
+
}
|
|
5236
|
+
return extractVerificationToken(body) === config.verificationToken;
|
|
5237
|
+
}
|
|
5238
|
+
function extractVerificationToken(body) {
|
|
5239
|
+
if (typeof body.token === "string") {
|
|
5240
|
+
return body.token;
|
|
5241
|
+
}
|
|
5242
|
+
if (typeof body.header?.token === "string") {
|
|
5243
|
+
return body.header.token;
|
|
5244
|
+
}
|
|
5245
|
+
return void 0;
|
|
5246
|
+
}
|
|
5247
|
+
|
|
5248
|
+
// src/channels/lark/routes.ts
|
|
5249
|
+
function registerLarkChannelRoutes(app2, dependencies) {
|
|
5250
|
+
const config = loadLarkIngressConfig();
|
|
5251
|
+
if (!dependencies && !isLarkIngressEnabled(config)) {
|
|
5252
|
+
return;
|
|
5253
|
+
}
|
|
5254
|
+
const handlerDependencies = dependencies || createDefaultLarkDependencies();
|
|
5255
|
+
app2.post(
|
|
5256
|
+
"/api/channels/lark/installations/:installationId/events",
|
|
5257
|
+
createLarkEventHandler({
|
|
5258
|
+
...handlerDependencies
|
|
5259
|
+
})
|
|
5260
|
+
);
|
|
5261
|
+
}
|
|
5262
|
+
function createDefaultLarkDependencies() {
|
|
5263
|
+
const installationStore = new PostgreSQLChannelInstallationStore({
|
|
5264
|
+
poolConfig: getDatabaseUrl()
|
|
5265
|
+
});
|
|
5266
|
+
const threadStore = getStoreLattice12("default", "thread").store;
|
|
5267
|
+
const mappingStore = new ChannelIdentityMappingStore({
|
|
5268
|
+
poolConfig: getDatabaseUrl()
|
|
5269
|
+
});
|
|
5270
|
+
const mappingService = createChannelThreadMappingService({
|
|
5271
|
+
mappingStore,
|
|
5272
|
+
threadStore
|
|
5273
|
+
});
|
|
5274
|
+
return {
|
|
5275
|
+
getInstallationConfig: async (installationId) => {
|
|
5276
|
+
const installation = await installationStore.getInstallationById(
|
|
5277
|
+
installationId
|
|
5278
|
+
);
|
|
5279
|
+
if (!installation || installation.channel !== "lark") {
|
|
5280
|
+
return null;
|
|
5281
|
+
}
|
|
5282
|
+
return {
|
|
5283
|
+
enabled: true,
|
|
5284
|
+
installationId: installation.id,
|
|
5285
|
+
tenantId: installation.tenantId,
|
|
5286
|
+
assistantId: installation.config.assistantId,
|
|
5287
|
+
appId: installation.config.appId,
|
|
5288
|
+
appSecret: installation.config.appSecret,
|
|
5289
|
+
verificationToken: installation.config.verificationToken,
|
|
5290
|
+
encryptKey: installation.config.encryptKey,
|
|
5291
|
+
workspaceId: installation.config.workspaceId,
|
|
5292
|
+
projectId: installation.config.projectId,
|
|
5293
|
+
mappingMode: installation.config.mappingMode
|
|
5294
|
+
};
|
|
5295
|
+
},
|
|
5296
|
+
parseRequestBody: (body, encryptKey) => parseLarkRequestBody(body, encryptKey),
|
|
5297
|
+
verifyParsedBody: (body, config) => {
|
|
5298
|
+
if (!config.verificationToken) {
|
|
5299
|
+
return true;
|
|
5300
|
+
}
|
|
5301
|
+
return createLarkRequestVerifier(config)({
|
|
5302
|
+
body
|
|
5303
|
+
});
|
|
5304
|
+
},
|
|
5305
|
+
parseEvent: parseLarkMessageEvent,
|
|
5306
|
+
claimInboundReceipt: (input) => mappingStore.claimInboundReceipt(input),
|
|
5307
|
+
markInboundReceiptCompleted: (input) => mappingStore.markInboundReceiptCompleted(input),
|
|
5308
|
+
markInboundReceiptFailed: (input) => mappingStore.markInboundReceiptFailed(input),
|
|
5309
|
+
resolveThread: (input) => mappingService.getOrCreateThread(input),
|
|
5310
|
+
runAgentAndCollectText: ({ tenantId, assistantId, threadId, text, workspaceId, projectId }) => runAgentAndCollectLarkReply({
|
|
5311
|
+
tenantId,
|
|
5312
|
+
assistantId,
|
|
5313
|
+
threadId,
|
|
5314
|
+
text,
|
|
5315
|
+
workspaceId,
|
|
5316
|
+
projectId
|
|
5317
|
+
}),
|
|
5318
|
+
sendTextReply: async ({ chatId, text, config }) => {
|
|
5319
|
+
const sender = createLarkSender({
|
|
5320
|
+
appId: config.appId,
|
|
5321
|
+
appSecret: config.appSecret
|
|
5322
|
+
});
|
|
5323
|
+
await sender.sendTextReply({ chatId, text });
|
|
5324
|
+
}
|
|
5325
|
+
};
|
|
5326
|
+
}
|
|
5327
|
+
function getDatabaseUrl() {
|
|
5328
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
5329
|
+
if (!databaseUrl) {
|
|
5330
|
+
throw new Error("DATABASE_URL is required for Lark channel ingress");
|
|
5331
|
+
}
|
|
5332
|
+
return databaseUrl;
|
|
5333
|
+
}
|
|
5334
|
+
|
|
5335
|
+
// src/channels/routes.ts
|
|
5336
|
+
var channelRouteRegistrars = [
|
|
5337
|
+
(app2, dependencies) => registerLarkChannelRoutes(app2, dependencies.lark)
|
|
5338
|
+
];
|
|
5339
|
+
function registerChannelRoutes(app2, dependencies = {}) {
|
|
5340
|
+
for (const registerRoutes of channelRouteRegistrars) {
|
|
5341
|
+
registerRoutes(app2, dependencies);
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
|
|
5345
|
+
// src/controllers/channel-installations.ts
|
|
5346
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
5347
|
+
function getTenantId9(request) {
|
|
5348
|
+
const userTenantId = request.user?.tenantId;
|
|
5349
|
+
if (userTenantId) {
|
|
5350
|
+
return userTenantId;
|
|
5351
|
+
}
|
|
5352
|
+
return request.headers["x-tenant-id"] || "default";
|
|
5353
|
+
}
|
|
5354
|
+
function getInstallationStore() {
|
|
5355
|
+
const { PostgreSQLChannelInstallationStore: PostgreSQLChannelInstallationStore2 } = __require("@axiom-lattice/pg-stores");
|
|
5356
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
5357
|
+
if (!databaseUrl) {
|
|
5358
|
+
throw new Error("DATABASE_URL is required for channel installation store");
|
|
5359
|
+
}
|
|
5360
|
+
return new PostgreSQLChannelInstallationStore2({
|
|
5361
|
+
poolConfig: databaseUrl
|
|
5362
|
+
});
|
|
5363
|
+
}
|
|
5364
|
+
async function getChannelInstallationList(request, reply) {
|
|
5365
|
+
const tenantId = getTenantId9(request);
|
|
5366
|
+
const { channel } = request.query;
|
|
5367
|
+
try {
|
|
5368
|
+
const store = getInstallationStore();
|
|
5369
|
+
const installations = await store.getInstallationsByTenant(tenantId, channel);
|
|
5370
|
+
return {
|
|
5371
|
+
success: true,
|
|
5372
|
+
message: "Channel installations retrieved successfully",
|
|
5373
|
+
data: {
|
|
5374
|
+
records: installations,
|
|
5375
|
+
total: installations.length
|
|
5376
|
+
}
|
|
5377
|
+
};
|
|
5378
|
+
} catch (error) {
|
|
5379
|
+
console.error("Failed to get channel installations:", error);
|
|
5380
|
+
return {
|
|
5381
|
+
success: false,
|
|
5382
|
+
message: "Failed to retrieve channel installations",
|
|
5383
|
+
data: {
|
|
5384
|
+
records: [],
|
|
5385
|
+
total: 0
|
|
5386
|
+
}
|
|
5387
|
+
};
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
async function getChannelInstallation(request, reply) {
|
|
5391
|
+
const tenantId = getTenantId9(request);
|
|
5392
|
+
const { installationId } = request.params;
|
|
5393
|
+
try {
|
|
5394
|
+
const store = getInstallationStore();
|
|
5395
|
+
const installation = await store.getInstallationById(installationId);
|
|
5396
|
+
if (!installation) {
|
|
5397
|
+
reply.code(404);
|
|
5398
|
+
return {
|
|
5399
|
+
success: false,
|
|
5400
|
+
message: "Channel installation not found"
|
|
5401
|
+
};
|
|
5402
|
+
}
|
|
5403
|
+
if (installation.tenantId !== tenantId) {
|
|
5404
|
+
reply.code(403);
|
|
5405
|
+
return {
|
|
5406
|
+
success: false,
|
|
5407
|
+
message: "Access denied"
|
|
5408
|
+
};
|
|
5409
|
+
}
|
|
5410
|
+
return {
|
|
5411
|
+
success: true,
|
|
5412
|
+
message: "Channel installation retrieved successfully",
|
|
5413
|
+
data: installation
|
|
5414
|
+
};
|
|
5415
|
+
} catch (error) {
|
|
5416
|
+
console.error("Failed to get channel installation:", error);
|
|
5417
|
+
return {
|
|
5418
|
+
success: false,
|
|
5419
|
+
message: "Failed to retrieve channel installation"
|
|
5420
|
+
};
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
async function createChannelInstallation(request, reply) {
|
|
5424
|
+
const tenantId = getTenantId9(request);
|
|
5425
|
+
const body = request.body;
|
|
5426
|
+
try {
|
|
5427
|
+
if (!body.channel) {
|
|
5428
|
+
reply.code(400);
|
|
5429
|
+
return {
|
|
5430
|
+
success: false,
|
|
5431
|
+
message: "Channel type is required"
|
|
5432
|
+
};
|
|
5433
|
+
}
|
|
5434
|
+
if (!body.config) {
|
|
5435
|
+
reply.code(400);
|
|
5436
|
+
return {
|
|
5437
|
+
success: false,
|
|
5438
|
+
message: "Configuration is required"
|
|
5439
|
+
};
|
|
5440
|
+
}
|
|
5441
|
+
if (body.channel === "lark") {
|
|
5442
|
+
const larkConfig = body.config;
|
|
5443
|
+
if (!larkConfig.appId || !larkConfig.appSecret) {
|
|
5444
|
+
reply.code(400);
|
|
5445
|
+
return {
|
|
5446
|
+
success: false,
|
|
5447
|
+
message: "appId and appSecret are required for Lark installations"
|
|
5448
|
+
};
|
|
5449
|
+
}
|
|
5450
|
+
if (!larkConfig.assistantId) {
|
|
5451
|
+
reply.code(400);
|
|
5452
|
+
return {
|
|
5453
|
+
success: false,
|
|
5454
|
+
message: "assistantId is required for Lark installations"
|
|
5455
|
+
};
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
const store = getInstallationStore();
|
|
5459
|
+
const installationId = body.id || randomUUID7();
|
|
5460
|
+
const installation = await store.createInstallation(
|
|
5461
|
+
tenantId,
|
|
5462
|
+
installationId,
|
|
5463
|
+
body
|
|
5464
|
+
);
|
|
5465
|
+
reply.code(201);
|
|
5466
|
+
return {
|
|
5467
|
+
success: true,
|
|
5468
|
+
message: "Channel installation created successfully",
|
|
5469
|
+
data: installation
|
|
5470
|
+
};
|
|
5471
|
+
} catch (error) {
|
|
5472
|
+
console.error("Failed to create channel installation:", error);
|
|
5473
|
+
if (error.message?.includes("duplicate") || error.code === "23505") {
|
|
5474
|
+
reply.code(409);
|
|
5475
|
+
return {
|
|
5476
|
+
success: false,
|
|
5477
|
+
message: "Channel installation with this ID already exists"
|
|
5478
|
+
};
|
|
5479
|
+
}
|
|
5480
|
+
return {
|
|
5481
|
+
success: false,
|
|
5482
|
+
message: "Failed to create channel installation"
|
|
5483
|
+
};
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5486
|
+
async function updateChannelInstallation(request, reply) {
|
|
5487
|
+
const tenantId = getTenantId9(request);
|
|
5488
|
+
const { installationId } = request.params;
|
|
5489
|
+
const body = request.body;
|
|
5490
|
+
try {
|
|
5491
|
+
const store = getInstallationStore();
|
|
5492
|
+
const existing = await store.getInstallationById(installationId);
|
|
5493
|
+
if (!existing) {
|
|
5494
|
+
reply.code(404);
|
|
5495
|
+
return {
|
|
5496
|
+
success: false,
|
|
5497
|
+
message: "Channel installation not found"
|
|
5498
|
+
};
|
|
5499
|
+
}
|
|
5500
|
+
if (existing.tenantId !== tenantId) {
|
|
5501
|
+
reply.code(403);
|
|
5502
|
+
return {
|
|
5503
|
+
success: false,
|
|
5504
|
+
message: "Access denied"
|
|
5505
|
+
};
|
|
5506
|
+
}
|
|
5507
|
+
const installation = await store.updateInstallation(
|
|
5508
|
+
tenantId,
|
|
5509
|
+
installationId,
|
|
5510
|
+
body
|
|
5511
|
+
);
|
|
5512
|
+
if (!installation) {
|
|
5513
|
+
reply.code(404);
|
|
5514
|
+
return {
|
|
5515
|
+
success: false,
|
|
5516
|
+
message: "Channel installation not found"
|
|
5517
|
+
};
|
|
5518
|
+
}
|
|
5519
|
+
return {
|
|
5520
|
+
success: true,
|
|
5521
|
+
message: "Channel installation updated successfully",
|
|
5522
|
+
data: installation
|
|
5523
|
+
};
|
|
5524
|
+
} catch (error) {
|
|
5525
|
+
console.error("Failed to update channel installation:", error);
|
|
5526
|
+
return {
|
|
5527
|
+
success: false,
|
|
5528
|
+
message: "Failed to update channel installation"
|
|
5529
|
+
};
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
async function deleteChannelInstallation(request, reply) {
|
|
5533
|
+
const tenantId = getTenantId9(request);
|
|
5534
|
+
const { installationId } = request.params;
|
|
5535
|
+
try {
|
|
5536
|
+
const store = getInstallationStore();
|
|
5537
|
+
const existing = await store.getInstallationById(installationId);
|
|
5538
|
+
if (!existing) {
|
|
5539
|
+
reply.code(404);
|
|
5540
|
+
return {
|
|
5541
|
+
success: false,
|
|
5542
|
+
message: "Channel installation not found"
|
|
5543
|
+
};
|
|
5544
|
+
}
|
|
5545
|
+
if (existing.tenantId !== tenantId) {
|
|
5546
|
+
reply.code(403);
|
|
5547
|
+
return {
|
|
5548
|
+
success: false,
|
|
5549
|
+
message: "Access denied"
|
|
5550
|
+
};
|
|
5551
|
+
}
|
|
5552
|
+
const deleted = await store.deleteInstallation(tenantId, installationId);
|
|
5553
|
+
if (!deleted) {
|
|
5554
|
+
reply.code(404);
|
|
5555
|
+
return {
|
|
5556
|
+
success: false,
|
|
5557
|
+
message: "Channel installation not found"
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
return {
|
|
5561
|
+
success: true,
|
|
5562
|
+
message: "Channel installation deleted successfully"
|
|
5563
|
+
};
|
|
5564
|
+
} catch (error) {
|
|
5565
|
+
console.error("Failed to delete channel installation:", error);
|
|
5566
|
+
return {
|
|
5567
|
+
success: false,
|
|
5568
|
+
message: "Failed to delete channel installation"
|
|
5569
|
+
};
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
|
|
5573
|
+
// src/routes/channel-installations.ts
|
|
5574
|
+
function registerChannelInstallationRoutes(app2) {
|
|
5575
|
+
app2.get("/api/channel-installations", getChannelInstallationList);
|
|
5576
|
+
app2.get("/api/channel-installations/:installationId", getChannelInstallation);
|
|
5577
|
+
app2.post("/api/channel-installations", createChannelInstallation);
|
|
5578
|
+
app2.put("/api/channel-installations/:installationId", updateChannelInstallation);
|
|
5579
|
+
app2.delete("/api/channel-installations/:installationId", deleteChannelInstallation);
|
|
5580
|
+
}
|
|
5581
|
+
|
|
4881
5582
|
// src/routes/index.ts
|
|
4882
5583
|
var registerLatticeRoutes = (app2) => {
|
|
4883
5584
|
app2.post("/api/runs", createRun);
|
|
@@ -5013,6 +5714,8 @@ var registerLatticeRoutes = (app2) => {
|
|
|
5013
5714
|
autoApproveUsers: process.env.AUTO_APPROVE_USERS !== "false",
|
|
5014
5715
|
allowTenantRegistration: process.env.ALLOW_TENANT_REGISTRATION !== "false"
|
|
5015
5716
|
});
|
|
5717
|
+
registerChannelRoutes(app2);
|
|
5718
|
+
registerChannelInstallationRoutes(app2);
|
|
5016
5719
|
app2.delete(
|
|
5017
5720
|
"/api/assistants/:assistant_id/threads/:thread_id/pending-messages/:message_id",
|
|
5018
5721
|
removePendingMessageHandler
|
|
@@ -5082,7 +5785,7 @@ var configureSwagger = async (app2, customSwaggerConfig, customSwaggerUiConfig)
|
|
|
5082
5785
|
};
|
|
5083
5786
|
|
|
5084
5787
|
// src/services/agent_task_consumer.ts
|
|
5085
|
-
import { eventBus as eventBus2, AGENT_TASK_EVENT, agentInstanceManager as
|
|
5788
|
+
import { eventBus as eventBus2, AGENT_TASK_EVENT, agentInstanceManager as agentInstanceManager6, QueueMode as QueueMode2 } from "@axiom-lattice/core";
|
|
5086
5789
|
var handleAgentTask = async (taskRequest, retryCount = 0) => {
|
|
5087
5790
|
const {
|
|
5088
5791
|
assistant_id,
|
|
@@ -5097,7 +5800,7 @@ var handleAgentTask = async (taskRequest, retryCount = 0) => {
|
|
|
5097
5800
|
console.log(
|
|
5098
5801
|
`\u5F00\u59CB\u5904\u7406\u4EFB\u52A1 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
|
|
5099
5802
|
);
|
|
5100
|
-
const agent =
|
|
5803
|
+
const agent = agentInstanceManager6.getAgent({ assistant_id, thread_id, tenant_id, workspace_id: runConfig?.workspaceId, project_id: runConfig?.projectId, custom_run_config: runConfig });
|
|
5101
5804
|
await agent.addMessage({ input, command, custom_run_config: runConfig }, QueueMode2.STEER);
|
|
5102
5805
|
if (callback_event) {
|
|
5103
5806
|
agent.subscribeOnce("message:completed", (evt) => {
|
|
@@ -5315,8 +6018,8 @@ import {
|
|
|
5315
6018
|
loggerLatticeManager,
|
|
5316
6019
|
sandboxLatticeManager as sandboxLatticeManager2,
|
|
5317
6020
|
sqlDatabaseManager as sqlDatabaseManager2,
|
|
5318
|
-
getStoreLattice as
|
|
5319
|
-
agentInstanceManager as
|
|
6021
|
+
getStoreLattice as getStoreLattice13,
|
|
6022
|
+
agentInstanceManager as agentInstanceManager7
|
|
5320
6023
|
} from "@axiom-lattice/core";
|
|
5321
6024
|
import {
|
|
5322
6025
|
LoggerType
|
|
@@ -5445,7 +6148,7 @@ var start = async (config) => {
|
|
|
5445
6148
|
app.decorate("loggerLattice", loggerLattice);
|
|
5446
6149
|
registerLatticeRoutes(app);
|
|
5447
6150
|
try {
|
|
5448
|
-
const storeLattice =
|
|
6151
|
+
const storeLattice = getStoreLattice13("default", "database");
|
|
5449
6152
|
const store = storeLattice.store;
|
|
5450
6153
|
sqlDatabaseManager2.setConfigStore(store);
|
|
5451
6154
|
logger.info("Database config store set for SqlDatabaseManager");
|
|
@@ -5477,7 +6180,7 @@ var start = async (config) => {
|
|
|
5477
6180
|
}
|
|
5478
6181
|
try {
|
|
5479
6182
|
logger.info("Starting agent instance recovery...");
|
|
5480
|
-
const restoreStats = await
|
|
6183
|
+
const restoreStats = await agentInstanceManager7.restore();
|
|
5481
6184
|
logger.info(`Agent recovery complete: ${restoreStats.restored} threads restored, ${restoreStats.errors} errors`);
|
|
5482
6185
|
} catch (error) {
|
|
5483
6186
|
logger.error("Agent recovery failed", { error });
|