@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.js
CHANGED
|
@@ -623,11 +623,13 @@ async function getThreadList(request, reply) {
|
|
|
623
623
|
const tenantId = getTenantId2(request);
|
|
624
624
|
const { assistantId } = request.params;
|
|
625
625
|
const metadataFilter = {};
|
|
626
|
-
|
|
627
|
-
|
|
626
|
+
const workspaceId = request.headers["x-workspace-id"];
|
|
627
|
+
const projectId = request.headers["x-project-id"];
|
|
628
|
+
if (workspaceId) {
|
|
629
|
+
metadataFilter.workspaceId = workspaceId;
|
|
628
630
|
}
|
|
629
|
-
if (
|
|
630
|
-
metadataFilter.projectId =
|
|
631
|
+
if (projectId) {
|
|
632
|
+
metadataFilter.projectId = projectId;
|
|
631
633
|
}
|
|
632
634
|
const storeLattice = (0, import_core6.getStoreLattice)("default", "thread");
|
|
633
635
|
const threadStore = storeLattice.store;
|
|
@@ -4892,6 +4894,695 @@ function registerAuthRoutes(app2, config) {
|
|
|
4892
4894
|
);
|
|
4893
4895
|
}
|
|
4894
4896
|
|
|
4897
|
+
// src/channels/lark/routes.ts
|
|
4898
|
+
var import_core27 = require("@axiom-lattice/core");
|
|
4899
|
+
var import_pg_stores = require("@axiom-lattice/pg-stores");
|
|
4900
|
+
|
|
4901
|
+
// src/channels/lark/parser.ts
|
|
4902
|
+
function parseLarkMessageEvent(payload) {
|
|
4903
|
+
const raw = payload;
|
|
4904
|
+
if (raw.header?.event_type !== "im.message.receive_v1") {
|
|
4905
|
+
return null;
|
|
4906
|
+
}
|
|
4907
|
+
const message = raw.event?.message;
|
|
4908
|
+
const openId = raw.event?.sender?.sender_id?.open_id;
|
|
4909
|
+
if (!message || !openId || message.message_type !== "text") {
|
|
4910
|
+
return null;
|
|
4911
|
+
}
|
|
4912
|
+
const parsedContent = parseLarkTextContent(message.content);
|
|
4913
|
+
if (!message.message_id || !message.chat_id || !parsedContent) {
|
|
4914
|
+
return null;
|
|
4915
|
+
}
|
|
4916
|
+
return {
|
|
4917
|
+
messageId: message.message_id,
|
|
4918
|
+
openId,
|
|
4919
|
+
chatId: message.chat_id,
|
|
4920
|
+
chatType: normalizeChatType(message.chat_type),
|
|
4921
|
+
text: parsedContent
|
|
4922
|
+
};
|
|
4923
|
+
}
|
|
4924
|
+
function parseLarkTextContent(content) {
|
|
4925
|
+
if (!content) {
|
|
4926
|
+
return null;
|
|
4927
|
+
}
|
|
4928
|
+
try {
|
|
4929
|
+
const parsed = JSON.parse(content);
|
|
4930
|
+
return typeof parsed.text === "string" ? parsed.text : null;
|
|
4931
|
+
} catch {
|
|
4932
|
+
return null;
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
function normalizeChatType(chatType) {
|
|
4936
|
+
return chatType === "p2p" ? "direct" : "group";
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4939
|
+
// src/channels/lark/controller.ts
|
|
4940
|
+
function createLarkEventHandler(dependencies) {
|
|
4941
|
+
return async function handleLarkEvent2(request, reply) {
|
|
4942
|
+
const installationId = request.params?.installationId;
|
|
4943
|
+
if (!installationId) {
|
|
4944
|
+
reply.status(400).send({ success: false, message: "Missing installationId" });
|
|
4945
|
+
return;
|
|
4946
|
+
}
|
|
4947
|
+
const config = await dependencies.getInstallationConfig(installationId);
|
|
4948
|
+
if (!config) {
|
|
4949
|
+
reply.status(404).send({ success: false, message: "Lark installation not found" });
|
|
4950
|
+
return;
|
|
4951
|
+
}
|
|
4952
|
+
const body = dependencies.parseRequestBody(
|
|
4953
|
+
request.body,
|
|
4954
|
+
config.encryptKey
|
|
4955
|
+
);
|
|
4956
|
+
if (!dependencies.verifyParsedBody(body, config)) {
|
|
4957
|
+
reply.status(401).send({ success: false, message: "Invalid Lark request" });
|
|
4958
|
+
return;
|
|
4959
|
+
}
|
|
4960
|
+
if (body.type === "url_verification" && body.challenge) {
|
|
4961
|
+
reply.status(200).send({ challenge: body.challenge });
|
|
4962
|
+
return;
|
|
4963
|
+
}
|
|
4964
|
+
const parsed = dependencies.parseEvent(request.body);
|
|
4965
|
+
if (!parsed) {
|
|
4966
|
+
reply.status(200).send({ success: true, ignored: true });
|
|
4967
|
+
return;
|
|
4968
|
+
}
|
|
4969
|
+
const receipt = await dependencies.claimInboundReceipt({
|
|
4970
|
+
channel: "lark",
|
|
4971
|
+
channelAppId: config.appId,
|
|
4972
|
+
externalMessageId: parsed.messageId,
|
|
4973
|
+
tenantId: config.tenantId
|
|
4974
|
+
});
|
|
4975
|
+
if (!receipt.accepted) {
|
|
4976
|
+
reply.status(200).send(
|
|
4977
|
+
receipt.status === "processing" ? { success: true, processing: true } : { success: true, duplicate: true }
|
|
4978
|
+
);
|
|
4979
|
+
return;
|
|
4980
|
+
}
|
|
4981
|
+
try {
|
|
4982
|
+
const { threadId } = await dependencies.resolveThread({
|
|
4983
|
+
channel: "lark",
|
|
4984
|
+
channelAppId: config.appId,
|
|
4985
|
+
tenantId: config.tenantId,
|
|
4986
|
+
assistantId: config.assistantId,
|
|
4987
|
+
mappingMode: config.mappingMode,
|
|
4988
|
+
openId: parsed.openId,
|
|
4989
|
+
chatId: parsed.chatId,
|
|
4990
|
+
chatType: parsed.chatType,
|
|
4991
|
+
messageId: parsed.messageId,
|
|
4992
|
+
workspaceId: config.workspaceId,
|
|
4993
|
+
projectId: config.projectId
|
|
4994
|
+
});
|
|
4995
|
+
const text = await dependencies.runAgentAndCollectText({
|
|
4996
|
+
tenantId: config.tenantId,
|
|
4997
|
+
assistantId: config.assistantId,
|
|
4998
|
+
threadId,
|
|
4999
|
+
text: parsed.text,
|
|
5000
|
+
workspaceId: config.workspaceId,
|
|
5001
|
+
projectId: config.projectId
|
|
5002
|
+
});
|
|
5003
|
+
await dependencies.sendTextReply({
|
|
5004
|
+
chatId: parsed.chatId,
|
|
5005
|
+
text,
|
|
5006
|
+
config
|
|
5007
|
+
});
|
|
5008
|
+
await dependencies.markInboundReceiptCompleted({
|
|
5009
|
+
channel: "lark",
|
|
5010
|
+
channelAppId: config.appId,
|
|
5011
|
+
externalMessageId: parsed.messageId,
|
|
5012
|
+
tenantId: config.tenantId,
|
|
5013
|
+
threadId
|
|
5014
|
+
});
|
|
5015
|
+
reply.status(200).send({ success: true, threadId });
|
|
5016
|
+
} catch (error) {
|
|
5017
|
+
await dependencies.markInboundReceiptFailed({
|
|
5018
|
+
channel: "lark",
|
|
5019
|
+
channelAppId: config.appId,
|
|
5020
|
+
externalMessageId: parsed.messageId,
|
|
5021
|
+
tenantId: config.tenantId
|
|
5022
|
+
});
|
|
5023
|
+
throw error;
|
|
5024
|
+
}
|
|
5025
|
+
};
|
|
5026
|
+
}
|
|
5027
|
+
var handleLarkEvent = createLarkEventHandler({
|
|
5028
|
+
getInstallationConfig: async () => null,
|
|
5029
|
+
parseRequestBody: (body) => body || {},
|
|
5030
|
+
verifyParsedBody: () => true,
|
|
5031
|
+
parseEvent: parseLarkMessageEvent,
|
|
5032
|
+
claimInboundReceipt: async () => ({ accepted: true, status: "processing" }),
|
|
5033
|
+
markInboundReceiptCompleted: async () => void 0,
|
|
5034
|
+
markInboundReceiptFailed: async () => void 0,
|
|
5035
|
+
resolveThread: async () => ({ threadId: "" }),
|
|
5036
|
+
runAgentAndCollectText: async () => "",
|
|
5037
|
+
sendTextReply: async () => void 0
|
|
5038
|
+
});
|
|
5039
|
+
|
|
5040
|
+
// src/channels/lark/config.ts
|
|
5041
|
+
function loadLarkIngressConfig() {
|
|
5042
|
+
return {
|
|
5043
|
+
enabled: process.env.LARK_ENABLED !== "false",
|
|
5044
|
+
appId: process.env.LARK_APP_ID || "",
|
|
5045
|
+
appSecret: process.env.LARK_APP_SECRET || "",
|
|
5046
|
+
verificationToken: process.env.LARK_VERIFICATION_TOKEN,
|
|
5047
|
+
encryptKey: process.env.LARK_ENCRYPT_KEY,
|
|
5048
|
+
tenantId: process.env.LARK_TENANT_ID || "default",
|
|
5049
|
+
assistantId: process.env.LARK_ASSISTANT_ID || "default_agent",
|
|
5050
|
+
workspaceId: process.env.LARK_WORKSPACE_ID,
|
|
5051
|
+
projectId: process.env.LARK_PROJECT_ID,
|
|
5052
|
+
mappingMode: process.env.LARK_MAPPING_MODE || "hybrid"
|
|
5053
|
+
};
|
|
5054
|
+
}
|
|
5055
|
+
function isLarkIngressEnabled(config) {
|
|
5056
|
+
return config.enabled;
|
|
5057
|
+
}
|
|
5058
|
+
|
|
5059
|
+
// src/channels/lark/mapping-service.ts
|
|
5060
|
+
var import_crypto6 = require("crypto");
|
|
5061
|
+
function createChannelThreadMappingService(deps) {
|
|
5062
|
+
return {
|
|
5063
|
+
async getOrCreateThread(input) {
|
|
5064
|
+
const externalSubjectKey = buildExternalSubjectKey(input);
|
|
5065
|
+
const existing = await deps.mappingStore.getMappingBySubject({
|
|
5066
|
+
channel: input.channel,
|
|
5067
|
+
channelAppId: input.channelAppId,
|
|
5068
|
+
tenantId: input.tenantId,
|
|
5069
|
+
assistantId: input.assistantId,
|
|
5070
|
+
externalSubjectKey
|
|
5071
|
+
});
|
|
5072
|
+
if (existing) {
|
|
5073
|
+
return { threadId: existing.threadId };
|
|
5074
|
+
}
|
|
5075
|
+
const threadId = (deps.uuid || import_crypto6.randomUUID)();
|
|
5076
|
+
await deps.threadStore.createThread(input.tenantId, input.assistantId, threadId, {
|
|
5077
|
+
metadata: {
|
|
5078
|
+
tenantId: input.tenantId,
|
|
5079
|
+
workspaceId: input.workspaceId,
|
|
5080
|
+
projectId: input.projectId,
|
|
5081
|
+
channel: input.channel,
|
|
5082
|
+
larkChatId: input.chatId,
|
|
5083
|
+
larkOpenId: input.openId
|
|
5084
|
+
}
|
|
5085
|
+
});
|
|
5086
|
+
try {
|
|
5087
|
+
await deps.mappingStore.createMapping({
|
|
5088
|
+
channel: input.channel,
|
|
5089
|
+
channelAppId: input.channelAppId,
|
|
5090
|
+
tenantId: input.tenantId,
|
|
5091
|
+
assistantId: input.assistantId,
|
|
5092
|
+
mappingMode: input.mappingMode,
|
|
5093
|
+
externalSubjectType: resolveSubjectType(input.mappingMode, input.chatType) === "user" ? "user" : "chat",
|
|
5094
|
+
externalSubjectKey,
|
|
5095
|
+
larkOpenId: input.openId,
|
|
5096
|
+
larkChatId: input.chatId,
|
|
5097
|
+
larkMessageId: input.messageId,
|
|
5098
|
+
threadId
|
|
5099
|
+
});
|
|
5100
|
+
} catch (error) {
|
|
5101
|
+
if (!isUniqueViolation(error)) {
|
|
5102
|
+
throw error;
|
|
5103
|
+
}
|
|
5104
|
+
const canonical = await deps.mappingStore.getMappingBySubject({
|
|
5105
|
+
channel: input.channel,
|
|
5106
|
+
channelAppId: input.channelAppId,
|
|
5107
|
+
tenantId: input.tenantId,
|
|
5108
|
+
assistantId: input.assistantId,
|
|
5109
|
+
externalSubjectKey
|
|
5110
|
+
});
|
|
5111
|
+
if (!canonical) {
|
|
5112
|
+
throw error;
|
|
5113
|
+
}
|
|
5114
|
+
await deps.threadStore.deleteThread(input.tenantId, threadId);
|
|
5115
|
+
return { threadId: canonical.threadId };
|
|
5116
|
+
}
|
|
5117
|
+
return { threadId };
|
|
5118
|
+
}
|
|
5119
|
+
};
|
|
5120
|
+
}
|
|
5121
|
+
function isUniqueViolation(error) {
|
|
5122
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
5123
|
+
}
|
|
5124
|
+
function buildExternalSubjectKey(input) {
|
|
5125
|
+
const subjectType = resolveSubjectType(input.mappingMode, input.chatType);
|
|
5126
|
+
const subjectValue = subjectType === "user" ? input.openId : input.chatId;
|
|
5127
|
+
return [
|
|
5128
|
+
input.channel,
|
|
5129
|
+
input.channelAppId,
|
|
5130
|
+
`tenant:${input.tenantId}`,
|
|
5131
|
+
`assistant:${input.assistantId}`,
|
|
5132
|
+
`${subjectType}:${subjectValue}`
|
|
5133
|
+
].join(":");
|
|
5134
|
+
}
|
|
5135
|
+
function resolveSubjectType(mappingMode, chatType) {
|
|
5136
|
+
if (mappingMode === "user") {
|
|
5137
|
+
return "user";
|
|
5138
|
+
}
|
|
5139
|
+
if (mappingMode === "group") {
|
|
5140
|
+
return "chat";
|
|
5141
|
+
}
|
|
5142
|
+
return chatType === "direct" ? "user" : "chat";
|
|
5143
|
+
}
|
|
5144
|
+
|
|
5145
|
+
// src/channels/lark/runner.ts
|
|
5146
|
+
var import_core26 = require("@axiom-lattice/core");
|
|
5147
|
+
var import_protocols4 = require("@axiom-lattice/protocols");
|
|
5148
|
+
|
|
5149
|
+
// src/channels/lark/aggregator.ts
|
|
5150
|
+
var import_protocols3 = require("@axiom-lattice/protocols");
|
|
5151
|
+
function aggregateLarkReply(messageId, chunks) {
|
|
5152
|
+
return chunks.filter(
|
|
5153
|
+
(chunk) => chunk.type === import_protocols3.MessageChunkTypes.AI && chunk.data.id === messageId
|
|
5154
|
+
).map((chunk) => chunk.data.content || "").join("").trim();
|
|
5155
|
+
}
|
|
5156
|
+
|
|
5157
|
+
// src/channels/lark/runner.ts
|
|
5158
|
+
async function runAgentAndCollectLarkReply(input) {
|
|
5159
|
+
const agent = import_core26.agentInstanceManager.getAgent({
|
|
5160
|
+
tenant_id: input.tenantId,
|
|
5161
|
+
assistant_id: input.assistantId,
|
|
5162
|
+
thread_id: input.threadId,
|
|
5163
|
+
workspace_id: input.workspaceId,
|
|
5164
|
+
project_id: input.projectId
|
|
5165
|
+
});
|
|
5166
|
+
const result = await agent.addMessage({
|
|
5167
|
+
input: {
|
|
5168
|
+
message: input.text
|
|
5169
|
+
}
|
|
5170
|
+
});
|
|
5171
|
+
const chunks = [];
|
|
5172
|
+
const stream = agent.chunkStream(result.messageId, [
|
|
5173
|
+
import_protocols4.MessageChunkTypes.MESSAGE_COMPLETED
|
|
5174
|
+
]);
|
|
5175
|
+
for await (const chunk of stream) {
|
|
5176
|
+
chunks.push(chunk);
|
|
5177
|
+
}
|
|
5178
|
+
return aggregateLarkReply(result.messageId, chunks);
|
|
5179
|
+
}
|
|
5180
|
+
|
|
5181
|
+
// src/channels/lark/sender.ts
|
|
5182
|
+
function createLarkSender(config, client = createDefaultLarkClient(config)) {
|
|
5183
|
+
return {
|
|
5184
|
+
async sendTextReply(input) {
|
|
5185
|
+
const response = await client.im.v1.message.create({
|
|
5186
|
+
params: {
|
|
5187
|
+
receive_id_type: "chat_id"
|
|
5188
|
+
},
|
|
5189
|
+
data: {
|
|
5190
|
+
receive_id: input.chatId,
|
|
5191
|
+
msg_type: "text",
|
|
5192
|
+
content: JSON.stringify({ text: input.text })
|
|
5193
|
+
}
|
|
5194
|
+
});
|
|
5195
|
+
if (response.code && response.code !== 0) {
|
|
5196
|
+
throw new Error("Failed to send Lark reply");
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
};
|
|
5200
|
+
}
|
|
5201
|
+
function createDefaultLarkClient(config) {
|
|
5202
|
+
const Lark = require("@larksuiteoapi/node-sdk");
|
|
5203
|
+
return new Lark.Client({
|
|
5204
|
+
appId: config.appId,
|
|
5205
|
+
appSecret: config.appSecret
|
|
5206
|
+
});
|
|
5207
|
+
}
|
|
5208
|
+
|
|
5209
|
+
// src/channels/lark/verification.ts
|
|
5210
|
+
var import_crypto7 = __toESM(require("crypto"));
|
|
5211
|
+
function parseLarkRequestBody(body, encryptKey) {
|
|
5212
|
+
const parsed = body || {};
|
|
5213
|
+
if (encryptKey && typeof parsed.encrypt === "string") {
|
|
5214
|
+
return decryptLarkPayload(encryptKey, parsed.encrypt);
|
|
5215
|
+
}
|
|
5216
|
+
return parsed;
|
|
5217
|
+
}
|
|
5218
|
+
function decryptLarkPayload(encryptKey, encryptedPayload) {
|
|
5219
|
+
const key = import_crypto7.default.createHash("sha256").update(encryptKey).digest();
|
|
5220
|
+
const buffer = Buffer.from(encryptedPayload, "base64");
|
|
5221
|
+
const iv = buffer.subarray(0, 16);
|
|
5222
|
+
const ciphertext = buffer.subarray(16);
|
|
5223
|
+
const decipher = import_crypto7.default.createDecipheriv("aes-256-cbc", key, iv);
|
|
5224
|
+
const plaintext = Buffer.concat([
|
|
5225
|
+
decipher.update(ciphertext),
|
|
5226
|
+
decipher.final()
|
|
5227
|
+
]).toString("utf8");
|
|
5228
|
+
return JSON.parse(plaintext);
|
|
5229
|
+
}
|
|
5230
|
+
function createLarkRequestVerifier(config) {
|
|
5231
|
+
return function verifyRequest(request) {
|
|
5232
|
+
const body = parseLarkRequestBody(request.body, config.encryptKey);
|
|
5233
|
+
return verifyLarkParsedBody(body, config);
|
|
5234
|
+
};
|
|
5235
|
+
}
|
|
5236
|
+
function verifyLarkParsedBody(body, config) {
|
|
5237
|
+
if (!config.verificationToken) {
|
|
5238
|
+
return true;
|
|
5239
|
+
}
|
|
5240
|
+
return extractVerificationToken(body) === config.verificationToken;
|
|
5241
|
+
}
|
|
5242
|
+
function extractVerificationToken(body) {
|
|
5243
|
+
if (typeof body.token === "string") {
|
|
5244
|
+
return body.token;
|
|
5245
|
+
}
|
|
5246
|
+
if (typeof body.header?.token === "string") {
|
|
5247
|
+
return body.header.token;
|
|
5248
|
+
}
|
|
5249
|
+
return void 0;
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
// src/channels/lark/routes.ts
|
|
5253
|
+
function registerLarkChannelRoutes(app2, dependencies) {
|
|
5254
|
+
const config = loadLarkIngressConfig();
|
|
5255
|
+
if (!dependencies && !isLarkIngressEnabled(config)) {
|
|
5256
|
+
return;
|
|
5257
|
+
}
|
|
5258
|
+
const handlerDependencies = dependencies || createDefaultLarkDependencies();
|
|
5259
|
+
app2.post(
|
|
5260
|
+
"/api/channels/lark/installations/:installationId/events",
|
|
5261
|
+
createLarkEventHandler({
|
|
5262
|
+
...handlerDependencies
|
|
5263
|
+
})
|
|
5264
|
+
);
|
|
5265
|
+
}
|
|
5266
|
+
function createDefaultLarkDependencies() {
|
|
5267
|
+
const installationStore = new import_pg_stores.PostgreSQLChannelInstallationStore({
|
|
5268
|
+
poolConfig: getDatabaseUrl()
|
|
5269
|
+
});
|
|
5270
|
+
const threadStore = (0, import_core27.getStoreLattice)("default", "thread").store;
|
|
5271
|
+
const mappingStore = new import_pg_stores.ChannelIdentityMappingStore({
|
|
5272
|
+
poolConfig: getDatabaseUrl()
|
|
5273
|
+
});
|
|
5274
|
+
const mappingService = createChannelThreadMappingService({
|
|
5275
|
+
mappingStore,
|
|
5276
|
+
threadStore
|
|
5277
|
+
});
|
|
5278
|
+
return {
|
|
5279
|
+
getInstallationConfig: async (installationId) => {
|
|
5280
|
+
const installation = await installationStore.getInstallationById(
|
|
5281
|
+
installationId
|
|
5282
|
+
);
|
|
5283
|
+
if (!installation || installation.channel !== "lark") {
|
|
5284
|
+
return null;
|
|
5285
|
+
}
|
|
5286
|
+
return {
|
|
5287
|
+
enabled: true,
|
|
5288
|
+
installationId: installation.id,
|
|
5289
|
+
tenantId: installation.tenantId,
|
|
5290
|
+
assistantId: installation.config.assistantId,
|
|
5291
|
+
appId: installation.config.appId,
|
|
5292
|
+
appSecret: installation.config.appSecret,
|
|
5293
|
+
verificationToken: installation.config.verificationToken,
|
|
5294
|
+
encryptKey: installation.config.encryptKey,
|
|
5295
|
+
workspaceId: installation.config.workspaceId,
|
|
5296
|
+
projectId: installation.config.projectId,
|
|
5297
|
+
mappingMode: installation.config.mappingMode
|
|
5298
|
+
};
|
|
5299
|
+
},
|
|
5300
|
+
parseRequestBody: (body, encryptKey) => parseLarkRequestBody(body, encryptKey),
|
|
5301
|
+
verifyParsedBody: (body, config) => {
|
|
5302
|
+
if (!config.verificationToken) {
|
|
5303
|
+
return true;
|
|
5304
|
+
}
|
|
5305
|
+
return createLarkRequestVerifier(config)({
|
|
5306
|
+
body
|
|
5307
|
+
});
|
|
5308
|
+
},
|
|
5309
|
+
parseEvent: parseLarkMessageEvent,
|
|
5310
|
+
claimInboundReceipt: (input) => mappingStore.claimInboundReceipt(input),
|
|
5311
|
+
markInboundReceiptCompleted: (input) => mappingStore.markInboundReceiptCompleted(input),
|
|
5312
|
+
markInboundReceiptFailed: (input) => mappingStore.markInboundReceiptFailed(input),
|
|
5313
|
+
resolveThread: (input) => mappingService.getOrCreateThread(input),
|
|
5314
|
+
runAgentAndCollectText: ({ tenantId, assistantId, threadId, text, workspaceId, projectId }) => runAgentAndCollectLarkReply({
|
|
5315
|
+
tenantId,
|
|
5316
|
+
assistantId,
|
|
5317
|
+
threadId,
|
|
5318
|
+
text,
|
|
5319
|
+
workspaceId,
|
|
5320
|
+
projectId
|
|
5321
|
+
}),
|
|
5322
|
+
sendTextReply: async ({ chatId, text, config }) => {
|
|
5323
|
+
const sender = createLarkSender({
|
|
5324
|
+
appId: config.appId,
|
|
5325
|
+
appSecret: config.appSecret
|
|
5326
|
+
});
|
|
5327
|
+
await sender.sendTextReply({ chatId, text });
|
|
5328
|
+
}
|
|
5329
|
+
};
|
|
5330
|
+
}
|
|
5331
|
+
function getDatabaseUrl() {
|
|
5332
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
5333
|
+
if (!databaseUrl) {
|
|
5334
|
+
throw new Error("DATABASE_URL is required for Lark channel ingress");
|
|
5335
|
+
}
|
|
5336
|
+
return databaseUrl;
|
|
5337
|
+
}
|
|
5338
|
+
|
|
5339
|
+
// src/channels/routes.ts
|
|
5340
|
+
var channelRouteRegistrars = [
|
|
5341
|
+
(app2, dependencies) => registerLarkChannelRoutes(app2, dependencies.lark)
|
|
5342
|
+
];
|
|
5343
|
+
function registerChannelRoutes(app2, dependencies = {}) {
|
|
5344
|
+
for (const registerRoutes of channelRouteRegistrars) {
|
|
5345
|
+
registerRoutes(app2, dependencies);
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
|
|
5349
|
+
// src/controllers/channel-installations.ts
|
|
5350
|
+
var import_crypto8 = require("crypto");
|
|
5351
|
+
function getTenantId9(request) {
|
|
5352
|
+
const userTenantId = request.user?.tenantId;
|
|
5353
|
+
if (userTenantId) {
|
|
5354
|
+
return userTenantId;
|
|
5355
|
+
}
|
|
5356
|
+
return request.headers["x-tenant-id"] || "default";
|
|
5357
|
+
}
|
|
5358
|
+
function getInstallationStore() {
|
|
5359
|
+
const { PostgreSQLChannelInstallationStore: PostgreSQLChannelInstallationStore2 } = require("@axiom-lattice/pg-stores");
|
|
5360
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
5361
|
+
if (!databaseUrl) {
|
|
5362
|
+
throw new Error("DATABASE_URL is required for channel installation store");
|
|
5363
|
+
}
|
|
5364
|
+
return new PostgreSQLChannelInstallationStore2({
|
|
5365
|
+
poolConfig: databaseUrl
|
|
5366
|
+
});
|
|
5367
|
+
}
|
|
5368
|
+
async function getChannelInstallationList(request, reply) {
|
|
5369
|
+
const tenantId = getTenantId9(request);
|
|
5370
|
+
const { channel } = request.query;
|
|
5371
|
+
try {
|
|
5372
|
+
const store = getInstallationStore();
|
|
5373
|
+
const installations = await store.getInstallationsByTenant(tenantId, channel);
|
|
5374
|
+
return {
|
|
5375
|
+
success: true,
|
|
5376
|
+
message: "Channel installations retrieved successfully",
|
|
5377
|
+
data: {
|
|
5378
|
+
records: installations,
|
|
5379
|
+
total: installations.length
|
|
5380
|
+
}
|
|
5381
|
+
};
|
|
5382
|
+
} catch (error) {
|
|
5383
|
+
console.error("Failed to get channel installations:", error);
|
|
5384
|
+
return {
|
|
5385
|
+
success: false,
|
|
5386
|
+
message: "Failed to retrieve channel installations",
|
|
5387
|
+
data: {
|
|
5388
|
+
records: [],
|
|
5389
|
+
total: 0
|
|
5390
|
+
}
|
|
5391
|
+
};
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
async function getChannelInstallation(request, reply) {
|
|
5395
|
+
const tenantId = getTenantId9(request);
|
|
5396
|
+
const { installationId } = request.params;
|
|
5397
|
+
try {
|
|
5398
|
+
const store = getInstallationStore();
|
|
5399
|
+
const installation = await store.getInstallationById(installationId);
|
|
5400
|
+
if (!installation) {
|
|
5401
|
+
reply.code(404);
|
|
5402
|
+
return {
|
|
5403
|
+
success: false,
|
|
5404
|
+
message: "Channel installation not found"
|
|
5405
|
+
};
|
|
5406
|
+
}
|
|
5407
|
+
if (installation.tenantId !== tenantId) {
|
|
5408
|
+
reply.code(403);
|
|
5409
|
+
return {
|
|
5410
|
+
success: false,
|
|
5411
|
+
message: "Access denied"
|
|
5412
|
+
};
|
|
5413
|
+
}
|
|
5414
|
+
return {
|
|
5415
|
+
success: true,
|
|
5416
|
+
message: "Channel installation retrieved successfully",
|
|
5417
|
+
data: installation
|
|
5418
|
+
};
|
|
5419
|
+
} catch (error) {
|
|
5420
|
+
console.error("Failed to get channel installation:", error);
|
|
5421
|
+
return {
|
|
5422
|
+
success: false,
|
|
5423
|
+
message: "Failed to retrieve channel installation"
|
|
5424
|
+
};
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
async function createChannelInstallation(request, reply) {
|
|
5428
|
+
const tenantId = getTenantId9(request);
|
|
5429
|
+
const body = request.body;
|
|
5430
|
+
try {
|
|
5431
|
+
if (!body.channel) {
|
|
5432
|
+
reply.code(400);
|
|
5433
|
+
return {
|
|
5434
|
+
success: false,
|
|
5435
|
+
message: "Channel type is required"
|
|
5436
|
+
};
|
|
5437
|
+
}
|
|
5438
|
+
if (!body.config) {
|
|
5439
|
+
reply.code(400);
|
|
5440
|
+
return {
|
|
5441
|
+
success: false,
|
|
5442
|
+
message: "Configuration is required"
|
|
5443
|
+
};
|
|
5444
|
+
}
|
|
5445
|
+
if (body.channel === "lark") {
|
|
5446
|
+
const larkConfig = body.config;
|
|
5447
|
+
if (!larkConfig.appId || !larkConfig.appSecret) {
|
|
5448
|
+
reply.code(400);
|
|
5449
|
+
return {
|
|
5450
|
+
success: false,
|
|
5451
|
+
message: "appId and appSecret are required for Lark installations"
|
|
5452
|
+
};
|
|
5453
|
+
}
|
|
5454
|
+
if (!larkConfig.assistantId) {
|
|
5455
|
+
reply.code(400);
|
|
5456
|
+
return {
|
|
5457
|
+
success: false,
|
|
5458
|
+
message: "assistantId is required for Lark installations"
|
|
5459
|
+
};
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
const store = getInstallationStore();
|
|
5463
|
+
const installationId = body.id || (0, import_crypto8.randomUUID)();
|
|
5464
|
+
const installation = await store.createInstallation(
|
|
5465
|
+
tenantId,
|
|
5466
|
+
installationId,
|
|
5467
|
+
body
|
|
5468
|
+
);
|
|
5469
|
+
reply.code(201);
|
|
5470
|
+
return {
|
|
5471
|
+
success: true,
|
|
5472
|
+
message: "Channel installation created successfully",
|
|
5473
|
+
data: installation
|
|
5474
|
+
};
|
|
5475
|
+
} catch (error) {
|
|
5476
|
+
console.error("Failed to create channel installation:", error);
|
|
5477
|
+
if (error.message?.includes("duplicate") || error.code === "23505") {
|
|
5478
|
+
reply.code(409);
|
|
5479
|
+
return {
|
|
5480
|
+
success: false,
|
|
5481
|
+
message: "Channel installation with this ID already exists"
|
|
5482
|
+
};
|
|
5483
|
+
}
|
|
5484
|
+
return {
|
|
5485
|
+
success: false,
|
|
5486
|
+
message: "Failed to create channel installation"
|
|
5487
|
+
};
|
|
5488
|
+
}
|
|
5489
|
+
}
|
|
5490
|
+
async function updateChannelInstallation(request, reply) {
|
|
5491
|
+
const tenantId = getTenantId9(request);
|
|
5492
|
+
const { installationId } = request.params;
|
|
5493
|
+
const body = request.body;
|
|
5494
|
+
try {
|
|
5495
|
+
const store = getInstallationStore();
|
|
5496
|
+
const existing = await store.getInstallationById(installationId);
|
|
5497
|
+
if (!existing) {
|
|
5498
|
+
reply.code(404);
|
|
5499
|
+
return {
|
|
5500
|
+
success: false,
|
|
5501
|
+
message: "Channel installation not found"
|
|
5502
|
+
};
|
|
5503
|
+
}
|
|
5504
|
+
if (existing.tenantId !== tenantId) {
|
|
5505
|
+
reply.code(403);
|
|
5506
|
+
return {
|
|
5507
|
+
success: false,
|
|
5508
|
+
message: "Access denied"
|
|
5509
|
+
};
|
|
5510
|
+
}
|
|
5511
|
+
const installation = await store.updateInstallation(
|
|
5512
|
+
tenantId,
|
|
5513
|
+
installationId,
|
|
5514
|
+
body
|
|
5515
|
+
);
|
|
5516
|
+
if (!installation) {
|
|
5517
|
+
reply.code(404);
|
|
5518
|
+
return {
|
|
5519
|
+
success: false,
|
|
5520
|
+
message: "Channel installation not found"
|
|
5521
|
+
};
|
|
5522
|
+
}
|
|
5523
|
+
return {
|
|
5524
|
+
success: true,
|
|
5525
|
+
message: "Channel installation updated successfully",
|
|
5526
|
+
data: installation
|
|
5527
|
+
};
|
|
5528
|
+
} catch (error) {
|
|
5529
|
+
console.error("Failed to update channel installation:", error);
|
|
5530
|
+
return {
|
|
5531
|
+
success: false,
|
|
5532
|
+
message: "Failed to update channel installation"
|
|
5533
|
+
};
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
async function deleteChannelInstallation(request, reply) {
|
|
5537
|
+
const tenantId = getTenantId9(request);
|
|
5538
|
+
const { installationId } = request.params;
|
|
5539
|
+
try {
|
|
5540
|
+
const store = getInstallationStore();
|
|
5541
|
+
const existing = await store.getInstallationById(installationId);
|
|
5542
|
+
if (!existing) {
|
|
5543
|
+
reply.code(404);
|
|
5544
|
+
return {
|
|
5545
|
+
success: false,
|
|
5546
|
+
message: "Channel installation not found"
|
|
5547
|
+
};
|
|
5548
|
+
}
|
|
5549
|
+
if (existing.tenantId !== tenantId) {
|
|
5550
|
+
reply.code(403);
|
|
5551
|
+
return {
|
|
5552
|
+
success: false,
|
|
5553
|
+
message: "Access denied"
|
|
5554
|
+
};
|
|
5555
|
+
}
|
|
5556
|
+
const deleted = await store.deleteInstallation(tenantId, installationId);
|
|
5557
|
+
if (!deleted) {
|
|
5558
|
+
reply.code(404);
|
|
5559
|
+
return {
|
|
5560
|
+
success: false,
|
|
5561
|
+
message: "Channel installation not found"
|
|
5562
|
+
};
|
|
5563
|
+
}
|
|
5564
|
+
return {
|
|
5565
|
+
success: true,
|
|
5566
|
+
message: "Channel installation deleted successfully"
|
|
5567
|
+
};
|
|
5568
|
+
} catch (error) {
|
|
5569
|
+
console.error("Failed to delete channel installation:", error);
|
|
5570
|
+
return {
|
|
5571
|
+
success: false,
|
|
5572
|
+
message: "Failed to delete channel installation"
|
|
5573
|
+
};
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
|
|
5577
|
+
// src/routes/channel-installations.ts
|
|
5578
|
+
function registerChannelInstallationRoutes(app2) {
|
|
5579
|
+
app2.get("/api/channel-installations", getChannelInstallationList);
|
|
5580
|
+
app2.get("/api/channel-installations/:installationId", getChannelInstallation);
|
|
5581
|
+
app2.post("/api/channel-installations", createChannelInstallation);
|
|
5582
|
+
app2.put("/api/channel-installations/:installationId", updateChannelInstallation);
|
|
5583
|
+
app2.delete("/api/channel-installations/:installationId", deleteChannelInstallation);
|
|
5584
|
+
}
|
|
5585
|
+
|
|
4895
5586
|
// src/routes/index.ts
|
|
4896
5587
|
var registerLatticeRoutes = (app2) => {
|
|
4897
5588
|
app2.post("/api/runs", createRun);
|
|
@@ -5027,6 +5718,8 @@ var registerLatticeRoutes = (app2) => {
|
|
|
5027
5718
|
autoApproveUsers: process.env.AUTO_APPROVE_USERS !== "false",
|
|
5028
5719
|
allowTenantRegistration: process.env.ALLOW_TENANT_REGISTRATION !== "false"
|
|
5029
5720
|
});
|
|
5721
|
+
registerChannelRoutes(app2);
|
|
5722
|
+
registerChannelInstallationRoutes(app2);
|
|
5030
5723
|
app2.delete(
|
|
5031
5724
|
"/api/assistants/:assistant_id/threads/:thread_id/pending-messages/:message_id",
|
|
5032
5725
|
removePendingMessageHandler
|
|
@@ -5096,7 +5789,7 @@ var configureSwagger = async (app2, customSwaggerConfig, customSwaggerUiConfig)
|
|
|
5096
5789
|
};
|
|
5097
5790
|
|
|
5098
5791
|
// src/services/agent_task_consumer.ts
|
|
5099
|
-
var
|
|
5792
|
+
var import_core28 = require("@axiom-lattice/core");
|
|
5100
5793
|
var handleAgentTask = async (taskRequest, retryCount = 0) => {
|
|
5101
5794
|
const {
|
|
5102
5795
|
assistant_id,
|
|
@@ -5111,18 +5804,18 @@ var handleAgentTask = async (taskRequest, retryCount = 0) => {
|
|
|
5111
5804
|
console.log(
|
|
5112
5805
|
`\u5F00\u59CB\u5904\u7406\u4EFB\u52A1 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
|
|
5113
5806
|
);
|
|
5114
|
-
const agent =
|
|
5115
|
-
await agent.addMessage({ input, command, custom_run_config: runConfig },
|
|
5807
|
+
const agent = import_core28.agentInstanceManager.getAgent({ assistant_id, thread_id, tenant_id, workspace_id: runConfig?.workspaceId, project_id: runConfig?.projectId, custom_run_config: runConfig });
|
|
5808
|
+
await agent.addMessage({ input, command, custom_run_config: runConfig }, import_core28.QueueMode.STEER);
|
|
5116
5809
|
if (callback_event) {
|
|
5117
5810
|
agent.subscribeOnce("message:completed", (evt) => {
|
|
5118
|
-
|
|
5811
|
+
import_core28.eventBus.publish(callback_event, {
|
|
5119
5812
|
success: true,
|
|
5120
5813
|
state: evt.state,
|
|
5121
5814
|
config: { assistant_id, thread_id, tenant_id }
|
|
5122
5815
|
});
|
|
5123
5816
|
});
|
|
5124
5817
|
agent.subscribeOnce("message:interrupted", (evt) => {
|
|
5125
|
-
|
|
5818
|
+
import_core28.eventBus.publish(callback_event, {
|
|
5126
5819
|
success: true,
|
|
5127
5820
|
state: evt.state,
|
|
5128
5821
|
config: { assistant_id, thread_id, tenant_id }
|
|
@@ -5146,7 +5839,7 @@ var handleAgentTask = async (taskRequest, retryCount = 0) => {
|
|
|
5146
5839
|
return handleAgentTask(taskRequest, nextRetryCount);
|
|
5147
5840
|
}
|
|
5148
5841
|
if (callback_event) {
|
|
5149
|
-
|
|
5842
|
+
import_core28.eventBus.publish(callback_event, {
|
|
5150
5843
|
success: false,
|
|
5151
5844
|
error: error instanceof Error ? error.message : String(error),
|
|
5152
5845
|
config: { assistant_id, thread_id, tenant_id }
|
|
@@ -5184,7 +5877,7 @@ var _AgentTaskConsumer = class _AgentTaskConsumer {
|
|
|
5184
5877
|
* 初始化事件监听和队列轮询
|
|
5185
5878
|
*/
|
|
5186
5879
|
initialize() {
|
|
5187
|
-
|
|
5880
|
+
import_core28.eventBus.subscribe(import_core28.AGENT_TASK_EVENT, this.trigger_agent_task.bind(this));
|
|
5188
5881
|
this.startPollingQueue();
|
|
5189
5882
|
console.log("Agent\u4EFB\u52A1\u6D88\u8D39\u8005\u5DF2\u542F\u52A8\u5E76\u76D1\u542C\u4EFB\u52A1\u4E8B\u4EF6\u548C\u961F\u5217");
|
|
5190
5883
|
}
|
|
@@ -5303,7 +5996,7 @@ var _AgentTaskConsumer = class _AgentTaskConsumer {
|
|
|
5303
5996
|
handleAgentTask(taskRequest).catch((error) => {
|
|
5304
5997
|
console.error("\u5904\u7406Agent\u4EFB\u52A1\u65F6\u53D1\u751F\u672A\u6355\u83B7\u7684\u9519\u8BEF:", error);
|
|
5305
5998
|
if (taskRequest.callback_event) {
|
|
5306
|
-
|
|
5999
|
+
import_core28.eventBus.publish(taskRequest.callback_event, {
|
|
5307
6000
|
success: false,
|
|
5308
6001
|
error: error instanceof Error ? error.message : String(error),
|
|
5309
6002
|
config: {
|
|
@@ -5323,26 +6016,26 @@ _AgentTaskConsumer.agent_run_endpoint = "http://localhost:4001/api/runs";
|
|
|
5323
6016
|
var AgentTaskConsumer = _AgentTaskConsumer;
|
|
5324
6017
|
|
|
5325
6018
|
// src/index.ts
|
|
5326
|
-
var
|
|
5327
|
-
var
|
|
6019
|
+
var import_core29 = require("@axiom-lattice/core");
|
|
6020
|
+
var import_protocols5 = require("@axiom-lattice/protocols");
|
|
5328
6021
|
process.on("unhandledRejection", (reason, promise) => {
|
|
5329
6022
|
console.error("\u672A\u5904\u7406\u7684Promise\u62D2\u7EDD:", reason);
|
|
5330
6023
|
});
|
|
5331
6024
|
var DEFAULT_LOGGER_CONFIG = {
|
|
5332
6025
|
name: "default",
|
|
5333
6026
|
description: "Default logger for lattice-gateway service",
|
|
5334
|
-
type:
|
|
6027
|
+
type: import_protocols5.LoggerType.PINO,
|
|
5335
6028
|
serviceName: "lattice/gateway",
|
|
5336
6029
|
loggerName: "lattice/gateway"
|
|
5337
6030
|
};
|
|
5338
6031
|
var loggerLattice = initializeLogger(DEFAULT_LOGGER_CONFIG);
|
|
5339
6032
|
var logger = loggerLattice.client;
|
|
5340
6033
|
function initializeLogger(config) {
|
|
5341
|
-
if (
|
|
5342
|
-
|
|
6034
|
+
if (import_core29.loggerLatticeManager.hasLattice("default")) {
|
|
6035
|
+
import_core29.loggerLatticeManager.removeLattice("default");
|
|
5343
6036
|
}
|
|
5344
|
-
(0,
|
|
5345
|
-
return (0,
|
|
6037
|
+
(0, import_core29.registerLoggerLattice)("default", config);
|
|
6038
|
+
return (0, import_core29.getLoggerLattice)("default");
|
|
5346
6039
|
}
|
|
5347
6040
|
var app = (0, import_fastify.default)({
|
|
5348
6041
|
logger: false,
|
|
@@ -5449,16 +6142,16 @@ var start = async (config) => {
|
|
|
5449
6142
|
app.decorate("loggerLattice", loggerLattice);
|
|
5450
6143
|
registerLatticeRoutes(app);
|
|
5451
6144
|
try {
|
|
5452
|
-
const storeLattice = (0,
|
|
6145
|
+
const storeLattice = (0, import_core29.getStoreLattice)("default", "database");
|
|
5453
6146
|
const store = storeLattice.store;
|
|
5454
|
-
|
|
6147
|
+
import_core29.sqlDatabaseManager.setConfigStore(store);
|
|
5455
6148
|
logger.info("Database config store set for SqlDatabaseManager");
|
|
5456
6149
|
} catch (error) {
|
|
5457
6150
|
logger.warn("Failed to set database config store: " + (error instanceof Error ? error.message : String(error)));
|
|
5458
6151
|
}
|
|
5459
|
-
if (!
|
|
6152
|
+
if (!import_core29.sandboxLatticeManager.hasLattice("default")) {
|
|
5460
6153
|
const sandboxBaseURL = process.env.SANDBOX_BASE_URL || "http://localhost:8080";
|
|
5461
|
-
|
|
6154
|
+
import_core29.sandboxLatticeManager.registerLattice("default", {
|
|
5462
6155
|
baseURL: sandboxBaseURL
|
|
5463
6156
|
});
|
|
5464
6157
|
logger.info(`Registered sandbox manager with baseURL: ${sandboxBaseURL}`);
|
|
@@ -5481,7 +6174,7 @@ var start = async (config) => {
|
|
|
5481
6174
|
}
|
|
5482
6175
|
try {
|
|
5483
6176
|
logger.info("Starting agent instance recovery...");
|
|
5484
|
-
const restoreStats = await
|
|
6177
|
+
const restoreStats = await import_core29.agentInstanceManager.restore();
|
|
5485
6178
|
logger.info(`Agent recovery complete: ${restoreStats.restored} threads restored, ${restoreStats.errors} errors`);
|
|
5486
6179
|
} catch (error) {
|
|
5487
6180
|
logger.error("Agent recovery failed", { error });
|