@axiom-lattice/gateway 2.1.74 → 2.1.76
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 +14 -12
- package/CHANGELOG.md +21 -0
- package/dist/index.d.mts +38 -2
- package/dist/index.d.ts +38 -2
- package/dist/index.js +741 -401
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +689 -390
- package/dist/index.mjs.map +1 -1
- package/dist/sender-PX32VSHB.mjs +32 -0
- package/dist/sender-PX32VSHB.mjs.map +1 -0
- package/package.json +7 -6
- package/src/bindings/index.ts +14 -0
- package/src/channels/__tests__/routes.test.ts +23 -44
- package/src/channels/lark/LarkChannelAdapter.ts +75 -0
- package/src/channels/lark/__tests__/controller.test.ts +62 -196
- package/src/channels/lark/controller.ts +25 -167
- package/src/channels/lark/routes.ts +12 -111
- package/src/channels/registry.ts +20 -0
- package/src/channels/routes.ts +7 -4
- package/src/controllers/channel-bindings.ts +135 -0
- package/src/controllers/channel-installations.ts +6 -3
- package/src/index.ts +38 -2
- package/src/router/MessageContext.ts +14 -0
- package/src/router/MessageRouter.ts +201 -0
- package/src/router/middlewares/auditLogger.ts +34 -0
- package/src/router/middlewares/deduplication.ts +26 -0
- package/src/router/middlewares/index.ts +3 -0
- package/src/router/middlewares/rateLimit.ts +39 -0
- package/src/routes/channel-bindings.ts +11 -0
- package/src/routes/index.ts +50 -2
package/dist/index.mjs
CHANGED
|
@@ -5700,12 +5700,8 @@ function registerAuthRoutes(app2, config) {
|
|
|
5700
5700
|
);
|
|
5701
5701
|
}
|
|
5702
5702
|
|
|
5703
|
-
// src/channels/lark/
|
|
5704
|
-
import {
|
|
5705
|
-
import {
|
|
5706
|
-
ChannelIdentityMappingStore,
|
|
5707
|
-
PostgreSQLChannelInstallationStore
|
|
5708
|
-
} from "@axiom-lattice/pg-stores";
|
|
5703
|
+
// src/channels/lark/LarkChannelAdapter.ts
|
|
5704
|
+
import { z } from "zod";
|
|
5709
5705
|
|
|
5710
5706
|
// src/channels/lark/parser.ts
|
|
5711
5707
|
function parseLarkMessageEvent(payload) {
|
|
@@ -5745,276 +5741,59 @@ function normalizeChatType(chatType) {
|
|
|
5745
5741
|
return chatType === "p2p" ? "direct" : "group";
|
|
5746
5742
|
}
|
|
5747
5743
|
|
|
5748
|
-
// src/channels/lark/
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
return;
|
|
5755
|
-
}
|
|
5756
|
-
const config = await dependencies.getInstallationConfig(installationId);
|
|
5757
|
-
if (!config) {
|
|
5758
|
-
reply.status(404).send({ success: false, message: "Lark installation not found" });
|
|
5759
|
-
return;
|
|
5760
|
-
}
|
|
5761
|
-
const body = dependencies.parseRequestBody(
|
|
5762
|
-
request.body,
|
|
5763
|
-
config.encryptKey
|
|
5764
|
-
);
|
|
5765
|
-
if (!dependencies.verifyParsedBody(body, config)) {
|
|
5766
|
-
reply.status(401).send({ success: false, message: "Invalid Lark request" });
|
|
5767
|
-
return;
|
|
5768
|
-
}
|
|
5769
|
-
if (body.type === "url_verification" && body.challenge) {
|
|
5770
|
-
reply.status(200).send({ challenge: body.challenge });
|
|
5771
|
-
return;
|
|
5772
|
-
}
|
|
5773
|
-
const parsed = dependencies.parseEvent(request.body);
|
|
5774
|
-
if (!parsed) {
|
|
5775
|
-
reply.status(200).send({ success: true, ignored: true });
|
|
5776
|
-
return;
|
|
5777
|
-
}
|
|
5778
|
-
const receipt = await dependencies.claimInboundReceipt({
|
|
5779
|
-
channel: "lark",
|
|
5780
|
-
channelAppId: config.appId,
|
|
5781
|
-
externalMessageId: parsed.messageId,
|
|
5782
|
-
tenantId: config.tenantId
|
|
5783
|
-
});
|
|
5784
|
-
if (!receipt.accepted) {
|
|
5785
|
-
reply.status(200).send(
|
|
5786
|
-
receipt.status === "processing" ? { success: true, processing: true } : { success: true, duplicate: true }
|
|
5787
|
-
);
|
|
5788
|
-
return;
|
|
5789
|
-
}
|
|
5790
|
-
try {
|
|
5791
|
-
const { threadId } = await dependencies.resolveThread({
|
|
5792
|
-
channel: "lark",
|
|
5793
|
-
channelAppId: config.appId,
|
|
5794
|
-
tenantId: config.tenantId,
|
|
5795
|
-
assistantId: config.assistantId,
|
|
5796
|
-
mappingMode: config.mappingMode,
|
|
5797
|
-
openId: parsed.openId,
|
|
5798
|
-
chatId: parsed.chatId,
|
|
5799
|
-
chatType: parsed.chatType,
|
|
5800
|
-
messageId: parsed.messageId,
|
|
5801
|
-
workspaceId: config.workspaceId,
|
|
5802
|
-
projectId: config.projectId
|
|
5803
|
-
});
|
|
5804
|
-
const text = await dependencies.runAgentAndCollectText({
|
|
5805
|
-
tenantId: config.tenantId,
|
|
5806
|
-
assistantId: config.assistantId,
|
|
5807
|
-
threadId,
|
|
5808
|
-
text: parsed.text,
|
|
5809
|
-
workspaceId: config.workspaceId,
|
|
5810
|
-
projectId: config.projectId
|
|
5811
|
-
});
|
|
5812
|
-
await dependencies.sendTextReply({
|
|
5813
|
-
chatId: parsed.chatId,
|
|
5814
|
-
text,
|
|
5815
|
-
config
|
|
5816
|
-
});
|
|
5817
|
-
await dependencies.markInboundReceiptCompleted({
|
|
5818
|
-
channel: "lark",
|
|
5819
|
-
channelAppId: config.appId,
|
|
5820
|
-
externalMessageId: parsed.messageId,
|
|
5821
|
-
tenantId: config.tenantId,
|
|
5822
|
-
threadId
|
|
5823
|
-
});
|
|
5824
|
-
reply.status(200).send({ success: true, threadId });
|
|
5825
|
-
} catch (error) {
|
|
5826
|
-
await dependencies.markInboundReceiptFailed({
|
|
5827
|
-
channel: "lark",
|
|
5828
|
-
channelAppId: config.appId,
|
|
5829
|
-
externalMessageId: parsed.messageId,
|
|
5830
|
-
tenantId: config.tenantId
|
|
5831
|
-
});
|
|
5832
|
-
throw error;
|
|
5833
|
-
}
|
|
5834
|
-
};
|
|
5835
|
-
}
|
|
5836
|
-
var handleLarkEvent = createLarkEventHandler({
|
|
5837
|
-
getInstallationConfig: async () => null,
|
|
5838
|
-
parseRequestBody: (body) => body || {},
|
|
5839
|
-
verifyParsedBody: () => true,
|
|
5840
|
-
parseEvent: parseLarkMessageEvent,
|
|
5841
|
-
claimInboundReceipt: async () => ({ accepted: true, status: "processing" }),
|
|
5842
|
-
markInboundReceiptCompleted: async () => void 0,
|
|
5843
|
-
markInboundReceiptFailed: async () => void 0,
|
|
5844
|
-
resolveThread: async () => ({ threadId: "" }),
|
|
5845
|
-
runAgentAndCollectText: async () => "",
|
|
5846
|
-
sendTextReply: async () => void 0
|
|
5744
|
+
// src/channels/lark/LarkChannelAdapter.ts
|
|
5745
|
+
var larkConfigSchema = z.object({
|
|
5746
|
+
appId: z.string(),
|
|
5747
|
+
appSecret: z.string(),
|
|
5748
|
+
verificationToken: z.string().optional(),
|
|
5749
|
+
encryptKey: z.string().optional()
|
|
5847
5750
|
});
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
function isLarkIngressEnabled(config) {
|
|
5865
|
-
return config.enabled;
|
|
5866
|
-
}
|
|
5867
|
-
|
|
5868
|
-
// src/channels/lark/mapping-service.ts
|
|
5869
|
-
import { randomUUID as randomUUID6 } from "crypto";
|
|
5870
|
-
function createChannelThreadMappingService(deps) {
|
|
5871
|
-
return {
|
|
5872
|
-
async getOrCreateThread(input) {
|
|
5873
|
-
const externalSubjectKey = buildExternalSubjectKey(input);
|
|
5874
|
-
const existing = await deps.mappingStore.getMappingBySubject({
|
|
5875
|
-
channel: input.channel,
|
|
5876
|
-
channelAppId: input.channelAppId,
|
|
5877
|
-
tenantId: input.tenantId,
|
|
5878
|
-
assistantId: input.assistantId,
|
|
5879
|
-
externalSubjectKey
|
|
5880
|
-
});
|
|
5881
|
-
if (existing) {
|
|
5882
|
-
return { threadId: existing.threadId };
|
|
5883
|
-
}
|
|
5884
|
-
const threadId = (deps.uuid || randomUUID6)();
|
|
5885
|
-
await deps.threadStore.createThread(input.tenantId, input.assistantId, threadId, {
|
|
5751
|
+
var larkChannelAdapter = {
|
|
5752
|
+
channel: "lark",
|
|
5753
|
+
configSchema: larkConfigSchema,
|
|
5754
|
+
async receive(rawPayload, installation) {
|
|
5755
|
+
const event = parseLarkMessageEvent(rawPayload);
|
|
5756
|
+
if (!event) return null;
|
|
5757
|
+
return {
|
|
5758
|
+
channel: "lark",
|
|
5759
|
+
channelInstallationId: installation.id,
|
|
5760
|
+
tenantId: installation.tenantId,
|
|
5761
|
+
sender: {
|
|
5762
|
+
id: event.openId,
|
|
5763
|
+
displayName: void 0
|
|
5764
|
+
},
|
|
5765
|
+
content: {
|
|
5766
|
+
text: event.text,
|
|
5886
5767
|
metadata: {
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
channel: input.channel,
|
|
5891
|
-
larkChatId: input.chatId,
|
|
5892
|
-
larkOpenId: input.openId
|
|
5768
|
+
chatId: event.chatId,
|
|
5769
|
+
chatType: event.chatType,
|
|
5770
|
+
messageId: event.messageId
|
|
5893
5771
|
}
|
|
5894
|
-
}
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
larkMessageId: input.messageId,
|
|
5907
|
-
threadId
|
|
5908
|
-
});
|
|
5909
|
-
} catch (error) {
|
|
5910
|
-
if (!isUniqueViolation(error)) {
|
|
5911
|
-
throw error;
|
|
5912
|
-
}
|
|
5913
|
-
const canonical = await deps.mappingStore.getMappingBySubject({
|
|
5914
|
-
channel: input.channel,
|
|
5915
|
-
channelAppId: input.channelAppId,
|
|
5916
|
-
tenantId: input.tenantId,
|
|
5917
|
-
assistantId: input.assistantId,
|
|
5918
|
-
externalSubjectKey
|
|
5919
|
-
});
|
|
5920
|
-
if (!canonical) {
|
|
5921
|
-
throw error;
|
|
5772
|
+
},
|
|
5773
|
+
conversation: {
|
|
5774
|
+
id: event.chatId,
|
|
5775
|
+
type: event.chatType
|
|
5776
|
+
},
|
|
5777
|
+
replyTarget: {
|
|
5778
|
+
adapterChannel: "lark",
|
|
5779
|
+
channelInstallationId: installation.id,
|
|
5780
|
+
rawTarget: {
|
|
5781
|
+
chatId: event.chatId,
|
|
5782
|
+
messageId: event.messageId,
|
|
5783
|
+
chatType: event.chatType
|
|
5922
5784
|
}
|
|
5923
|
-
await deps.threadStore.deleteThread(input.tenantId, threadId);
|
|
5924
|
-
return { threadId: canonical.threadId };
|
|
5925
5785
|
}
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
}
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
const subjectValue = subjectType === "user" ? input.openId : input.chatId;
|
|
5936
|
-
return [
|
|
5937
|
-
input.channel,
|
|
5938
|
-
input.channelAppId,
|
|
5939
|
-
`tenant:${input.tenantId}`,
|
|
5940
|
-
`assistant:${input.assistantId}`,
|
|
5941
|
-
`${subjectType}:${subjectValue}`
|
|
5942
|
-
].join(":");
|
|
5943
|
-
}
|
|
5944
|
-
function resolveSubjectType(mappingMode, chatType) {
|
|
5945
|
-
if (mappingMode === "user") {
|
|
5946
|
-
return "user";
|
|
5947
|
-
}
|
|
5948
|
-
if (mappingMode === "group") {
|
|
5949
|
-
return "chat";
|
|
5950
|
-
}
|
|
5951
|
-
return chatType === "direct" ? "user" : "chat";
|
|
5952
|
-
}
|
|
5953
|
-
|
|
5954
|
-
// src/channels/lark/runner.ts
|
|
5955
|
-
import { agentInstanceManager as agentInstanceManager6 } from "@axiom-lattice/core";
|
|
5956
|
-
import { MessageChunkTypes as MessageChunkTypes4 } from "@axiom-lattice/protocols";
|
|
5957
|
-
|
|
5958
|
-
// src/channels/lark/aggregator.ts
|
|
5959
|
-
import { MessageChunkTypes as MessageChunkTypes3 } from "@axiom-lattice/protocols";
|
|
5960
|
-
function aggregateLarkReply(messageId, chunks) {
|
|
5961
|
-
return chunks.filter(
|
|
5962
|
-
(chunk) => chunk.type === MessageChunkTypes3.AI && chunk.data.id === messageId
|
|
5963
|
-
).map((chunk) => chunk.data.content || "").join("").trim();
|
|
5964
|
-
}
|
|
5965
|
-
|
|
5966
|
-
// src/channels/lark/runner.ts
|
|
5967
|
-
async function runAgentAndCollectLarkReply(input) {
|
|
5968
|
-
const agent = agentInstanceManager6.getAgent({
|
|
5969
|
-
tenant_id: input.tenantId,
|
|
5970
|
-
assistant_id: input.assistantId,
|
|
5971
|
-
thread_id: input.threadId,
|
|
5972
|
-
workspace_id: input.workspaceId,
|
|
5973
|
-
project_id: input.projectId
|
|
5974
|
-
});
|
|
5975
|
-
const result = await agent.addMessage({
|
|
5976
|
-
input: {
|
|
5977
|
-
message: input.text
|
|
5978
|
-
}
|
|
5979
|
-
});
|
|
5980
|
-
const chunks = [];
|
|
5981
|
-
const stream = agent.chunkStream(result.messageId, [
|
|
5982
|
-
MessageChunkTypes4.MESSAGE_COMPLETED
|
|
5983
|
-
]);
|
|
5984
|
-
for await (const chunk of stream) {
|
|
5985
|
-
chunks.push(chunk);
|
|
5786
|
+
};
|
|
5787
|
+
},
|
|
5788
|
+
async sendReply(replyTarget, message, installation) {
|
|
5789
|
+
const { createLarkSender } = await import("./sender-PX32VSHB.mjs");
|
|
5790
|
+
const sender = createLarkSender(installation.config);
|
|
5791
|
+
await sender.sendTextReply({
|
|
5792
|
+
chatId: replyTarget.rawTarget.chatId,
|
|
5793
|
+
text: message.text
|
|
5794
|
+
});
|
|
5986
5795
|
}
|
|
5987
|
-
|
|
5988
|
-
}
|
|
5989
|
-
|
|
5990
|
-
// src/channels/lark/sender.ts
|
|
5991
|
-
async function createLarkSender(config, client) {
|
|
5992
|
-
const resolved = client ?? await createDefaultLarkClient(config);
|
|
5993
|
-
return {
|
|
5994
|
-
async sendTextReply(input) {
|
|
5995
|
-
const response = await resolved.im.v1.message.create({
|
|
5996
|
-
params: {
|
|
5997
|
-
receive_id_type: "chat_id"
|
|
5998
|
-
},
|
|
5999
|
-
data: {
|
|
6000
|
-
receive_id: input.chatId,
|
|
6001
|
-
msg_type: "text",
|
|
6002
|
-
content: JSON.stringify({ text: input.text })
|
|
6003
|
-
}
|
|
6004
|
-
});
|
|
6005
|
-
if (response.code && response.code !== 0) {
|
|
6006
|
-
throw new Error("Failed to send Lark reply");
|
|
6007
|
-
}
|
|
6008
|
-
}
|
|
6009
|
-
};
|
|
6010
|
-
}
|
|
6011
|
-
async function createDefaultLarkClient(config) {
|
|
6012
|
-
const Lark = await import("@larksuiteoapi/node-sdk");
|
|
6013
|
-
return new Lark.Client({
|
|
6014
|
-
appId: config.appId,
|
|
6015
|
-
appSecret: config.appSecret
|
|
6016
|
-
});
|
|
6017
|
-
}
|
|
5796
|
+
};
|
|
6018
5797
|
|
|
6019
5798
|
// src/channels/lark/verification.ts
|
|
6020
5799
|
import crypto2 from "crypto";
|
|
@@ -6037,127 +5816,199 @@ function decryptLarkPayload(encryptKey, encryptedPayload) {
|
|
|
6037
5816
|
]).toString("utf8");
|
|
6038
5817
|
return JSON.parse(plaintext);
|
|
6039
5818
|
}
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
5819
|
+
|
|
5820
|
+
// src/logger/Logger.ts
|
|
5821
|
+
import pino from "pino";
|
|
5822
|
+
import "pino-pretty";
|
|
5823
|
+
import "pino-roll";
|
|
5824
|
+
var PinoLoggerFactory = class _PinoLoggerFactory {
|
|
5825
|
+
constructor() {
|
|
5826
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
5827
|
+
const loggerConfig = {
|
|
5828
|
+
// 自定义时间戳格式
|
|
5829
|
+
timestamp: () => `,"@timestamp":"${(/* @__PURE__ */ new Date()).toISOString()}"`,
|
|
5830
|
+
// 关闭默认的时间戳键
|
|
5831
|
+
base: {
|
|
5832
|
+
"@version": "1",
|
|
5833
|
+
app_name: "lattice",
|
|
5834
|
+
service_name: "lattice/graph-server",
|
|
5835
|
+
thread_name: "main",
|
|
5836
|
+
logger_name: "lattice-graph-logger"
|
|
5837
|
+
},
|
|
5838
|
+
formatters: {
|
|
5839
|
+
level: (label, number) => {
|
|
5840
|
+
return {
|
|
5841
|
+
level: label.toUpperCase(),
|
|
5842
|
+
level_value: number * 1e3
|
|
5843
|
+
};
|
|
5844
|
+
}
|
|
5845
|
+
}
|
|
5846
|
+
};
|
|
5847
|
+
if (isProd) {
|
|
5848
|
+
try {
|
|
5849
|
+
this.pinoLogger = pino(
|
|
5850
|
+
loggerConfig,
|
|
5851
|
+
pino.transport({
|
|
5852
|
+
target: "pino-roll",
|
|
5853
|
+
options: {
|
|
5854
|
+
file: "./logs/fin_ai_graph_server",
|
|
5855
|
+
frequency: "daily",
|
|
5856
|
+
mkdir: true
|
|
5857
|
+
}
|
|
5858
|
+
})
|
|
5859
|
+
);
|
|
5860
|
+
} catch (error) {
|
|
5861
|
+
console.error(
|
|
5862
|
+
"\u65E0\u6CD5\u521D\u59CB\u5316 pino-roll \u65E5\u5FD7\u8BB0\u5F55\u5668\uFF0C\u56DE\u9000\u5230\u63A7\u5236\u53F0\u65E5\u5FD7",
|
|
5863
|
+
error
|
|
5864
|
+
);
|
|
5865
|
+
this.pinoLogger = pino({
|
|
5866
|
+
...loggerConfig,
|
|
5867
|
+
transport: {
|
|
5868
|
+
target: "pino-pretty",
|
|
5869
|
+
options: {
|
|
5870
|
+
colorize: true
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
});
|
|
5874
|
+
}
|
|
5875
|
+
} else {
|
|
5876
|
+
this.pinoLogger = pino({
|
|
5877
|
+
...loggerConfig,
|
|
5878
|
+
transport: {
|
|
5879
|
+
target: "pino-pretty",
|
|
5880
|
+
options: {
|
|
5881
|
+
colorize: true
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
});
|
|
5885
|
+
}
|
|
6049
5886
|
}
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
return
|
|
5887
|
+
static getInstance() {
|
|
5888
|
+
if (!_PinoLoggerFactory.instance) {
|
|
5889
|
+
_PinoLoggerFactory.instance = new _PinoLoggerFactory();
|
|
5890
|
+
}
|
|
5891
|
+
return _PinoLoggerFactory.instance;
|
|
6055
5892
|
}
|
|
6056
|
-
|
|
6057
|
-
return
|
|
5893
|
+
getPinoLogger() {
|
|
5894
|
+
return this.pinoLogger;
|
|
6058
5895
|
}
|
|
6059
|
-
|
|
5896
|
+
};
|
|
5897
|
+
var Logger = class _Logger {
|
|
5898
|
+
constructor(options) {
|
|
5899
|
+
this.context = options?.context || {};
|
|
5900
|
+
this.name = options?.name || "lattice-graph-logger";
|
|
5901
|
+
this.serviceName = options?.serviceName || "lattice/graph-server";
|
|
5902
|
+
}
|
|
5903
|
+
/**
|
|
5904
|
+
* 获取合并了上下文的日志对象
|
|
5905
|
+
* @param additionalContext 额外的上下文数据
|
|
5906
|
+
* @returns 带有上下文的pino日志对象
|
|
5907
|
+
*/
|
|
5908
|
+
getContextualLogger(additionalContext) {
|
|
5909
|
+
const pinoLogger = PinoLoggerFactory.getInstance().getPinoLogger();
|
|
5910
|
+
const contextObj = {
|
|
5911
|
+
"x-user-id": this.context["x-user-id"] || "",
|
|
5912
|
+
"x-tenant-id": this.context["x-tenant-id"] || "",
|
|
5913
|
+
"x-request-id": this.context["x-request-id"] || "",
|
|
5914
|
+
"x-task-id": this.context["x-task-id"] || "",
|
|
5915
|
+
"x-thread-id": this.context["x-thread-id"] || "",
|
|
5916
|
+
service_name: this.serviceName,
|
|
5917
|
+
logger_name: this.name,
|
|
5918
|
+
...additionalContext
|
|
5919
|
+
};
|
|
5920
|
+
return pinoLogger.child(contextObj);
|
|
5921
|
+
}
|
|
5922
|
+
info(msg, obj) {
|
|
5923
|
+
this.getContextualLogger(obj).info(msg);
|
|
5924
|
+
}
|
|
5925
|
+
error(msg, obj) {
|
|
5926
|
+
this.getContextualLogger(obj).error(msg);
|
|
5927
|
+
}
|
|
5928
|
+
warn(msg, obj) {
|
|
5929
|
+
this.getContextualLogger(obj).warn(msg);
|
|
5930
|
+
}
|
|
5931
|
+
debug(msg, obj) {
|
|
5932
|
+
this.getContextualLogger(obj).debug(msg);
|
|
5933
|
+
}
|
|
5934
|
+
/**
|
|
5935
|
+
* 更新Logger实例的上下文
|
|
5936
|
+
*/
|
|
5937
|
+
updateContext(context) {
|
|
5938
|
+
this.context = {
|
|
5939
|
+
...this.context,
|
|
5940
|
+
...context
|
|
5941
|
+
};
|
|
5942
|
+
}
|
|
5943
|
+
/**
|
|
5944
|
+
* 创建一个新的Logger实例,继承当前Logger的上下文
|
|
5945
|
+
*/
|
|
5946
|
+
child(options) {
|
|
5947
|
+
return new _Logger({
|
|
5948
|
+
name: options.name || this.name,
|
|
5949
|
+
serviceName: options.serviceName || this.serviceName,
|
|
5950
|
+
context: {
|
|
5951
|
+
...this.context,
|
|
5952
|
+
...options.context
|
|
5953
|
+
}
|
|
5954
|
+
});
|
|
5955
|
+
}
|
|
5956
|
+
};
|
|
5957
|
+
|
|
5958
|
+
// src/channels/lark/controller.ts
|
|
5959
|
+
var logger = new Logger({ serviceName: "lattice/gateway/lark" });
|
|
5960
|
+
function createLarkEventHandler(deps) {
|
|
5961
|
+
return async function handleLarkEvent(request, reply) {
|
|
5962
|
+
const { installationId } = request.params;
|
|
5963
|
+
const installation = await deps.installationStore.getInstallationById(installationId);
|
|
5964
|
+
if (!installation || installation.channel !== "lark") {
|
|
5965
|
+
reply.status(404).send({ success: false, message: "Installation not found" });
|
|
5966
|
+
return;
|
|
5967
|
+
}
|
|
5968
|
+
const body = parseLarkRequestBody(request.body, installation.config.encryptKey);
|
|
5969
|
+
if (body.type === "url_verification" && body.challenge) {
|
|
5970
|
+
reply.status(200).send({ challenge: body.challenge });
|
|
5971
|
+
return;
|
|
5972
|
+
}
|
|
5973
|
+
const inboundMessage = await larkChannelAdapter.receive(request.body, installation);
|
|
5974
|
+
if (!inboundMessage) {
|
|
5975
|
+
reply.status(200).send();
|
|
5976
|
+
return;
|
|
5977
|
+
}
|
|
5978
|
+
deps.router.dispatch(inboundMessage).catch((error) => {
|
|
5979
|
+
logger.error("Lark message dispatch error", {
|
|
5980
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5981
|
+
});
|
|
5982
|
+
});
|
|
5983
|
+
reply.status(200).send();
|
|
5984
|
+
};
|
|
6060
5985
|
}
|
|
6061
5986
|
|
|
6062
5987
|
// src/channels/lark/routes.ts
|
|
6063
|
-
function registerLarkChannelRoutes(app2,
|
|
6064
|
-
const
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
}
|
|
6068
|
-
const handlerDependencies = dependencies || createDefaultLarkDependencies();
|
|
5988
|
+
function registerLarkChannelRoutes(app2, deps) {
|
|
5989
|
+
const handler = createLarkEventHandler({
|
|
5990
|
+
installationStore: deps.installationStore,
|
|
5991
|
+
router: deps.router
|
|
5992
|
+
});
|
|
6069
5993
|
app2.post(
|
|
6070
5994
|
"/api/channels/lark/installations/:installationId/events",
|
|
6071
|
-
|
|
6072
|
-
...handlerDependencies
|
|
6073
|
-
})
|
|
5995
|
+
handler
|
|
6074
5996
|
);
|
|
6075
5997
|
}
|
|
6076
|
-
function createDefaultLarkDependencies() {
|
|
6077
|
-
const installationStore = new PostgreSQLChannelInstallationStore({
|
|
6078
|
-
poolConfig: getDatabaseUrl()
|
|
6079
|
-
});
|
|
6080
|
-
const threadStore = getStoreLattice14("default", "thread").store;
|
|
6081
|
-
const mappingStore = new ChannelIdentityMappingStore({
|
|
6082
|
-
poolConfig: getDatabaseUrl()
|
|
6083
|
-
});
|
|
6084
|
-
const mappingService = createChannelThreadMappingService({
|
|
6085
|
-
mappingStore,
|
|
6086
|
-
threadStore
|
|
6087
|
-
});
|
|
6088
|
-
return {
|
|
6089
|
-
getInstallationConfig: async (installationId) => {
|
|
6090
|
-
const installation = await installationStore.getInstallationById(
|
|
6091
|
-
installationId
|
|
6092
|
-
);
|
|
6093
|
-
if (!installation || installation.channel !== "lark") {
|
|
6094
|
-
return null;
|
|
6095
|
-
}
|
|
6096
|
-
return {
|
|
6097
|
-
enabled: true,
|
|
6098
|
-
installationId: installation.id,
|
|
6099
|
-
tenantId: installation.tenantId,
|
|
6100
|
-
assistantId: installation.config.assistantId,
|
|
6101
|
-
appId: installation.config.appId,
|
|
6102
|
-
appSecret: installation.config.appSecret,
|
|
6103
|
-
verificationToken: installation.config.verificationToken,
|
|
6104
|
-
encryptKey: installation.config.encryptKey,
|
|
6105
|
-
workspaceId: installation.config.workspaceId,
|
|
6106
|
-
projectId: installation.config.projectId,
|
|
6107
|
-
mappingMode: installation.config.mappingMode
|
|
6108
|
-
};
|
|
6109
|
-
},
|
|
6110
|
-
parseRequestBody: (body, encryptKey) => parseLarkRequestBody(body, encryptKey),
|
|
6111
|
-
verifyParsedBody: (body, config) => {
|
|
6112
|
-
if (!config.verificationToken) {
|
|
6113
|
-
return true;
|
|
6114
|
-
}
|
|
6115
|
-
return createLarkRequestVerifier(config)({
|
|
6116
|
-
body
|
|
6117
|
-
});
|
|
6118
|
-
},
|
|
6119
|
-
parseEvent: parseLarkMessageEvent,
|
|
6120
|
-
claimInboundReceipt: (input) => mappingStore.claimInboundReceipt(input),
|
|
6121
|
-
markInboundReceiptCompleted: (input) => mappingStore.markInboundReceiptCompleted(input),
|
|
6122
|
-
markInboundReceiptFailed: (input) => mappingStore.markInboundReceiptFailed(input),
|
|
6123
|
-
resolveThread: (input) => mappingService.getOrCreateThread(input),
|
|
6124
|
-
runAgentAndCollectText: ({ tenantId, assistantId, threadId, text, workspaceId, projectId }) => runAgentAndCollectLarkReply({
|
|
6125
|
-
tenantId,
|
|
6126
|
-
assistantId,
|
|
6127
|
-
threadId,
|
|
6128
|
-
text,
|
|
6129
|
-
workspaceId,
|
|
6130
|
-
projectId
|
|
6131
|
-
}),
|
|
6132
|
-
sendTextReply: async ({ chatId, text, config }) => {
|
|
6133
|
-
const sender = await createLarkSender({
|
|
6134
|
-
appId: config.appId,
|
|
6135
|
-
appSecret: config.appSecret
|
|
6136
|
-
});
|
|
6137
|
-
await sender.sendTextReply({ chatId, text });
|
|
6138
|
-
}
|
|
6139
|
-
};
|
|
6140
|
-
}
|
|
6141
|
-
function getDatabaseUrl() {
|
|
6142
|
-
const databaseUrl = process.env.DATABASE_URL;
|
|
6143
|
-
if (!databaseUrl) {
|
|
6144
|
-
throw new Error("DATABASE_URL is required for Lark channel ingress");
|
|
6145
|
-
}
|
|
6146
|
-
return databaseUrl;
|
|
6147
|
-
}
|
|
6148
5998
|
|
|
6149
5999
|
// src/channels/routes.ts
|
|
6150
6000
|
var channelRouteRegistrars = [
|
|
6151
|
-
(app2,
|
|
6001
|
+
(app2, deps) => registerLarkChannelRoutes(app2, deps)
|
|
6152
6002
|
];
|
|
6153
|
-
function registerChannelRoutes(app2, dependencies
|
|
6003
|
+
function registerChannelRoutes(app2, dependencies) {
|
|
6004
|
+
if (!dependencies) return;
|
|
6154
6005
|
for (const registerRoutes of channelRouteRegistrars) {
|
|
6155
6006
|
registerRoutes(app2, dependencies);
|
|
6156
6007
|
}
|
|
6157
6008
|
}
|
|
6158
6009
|
|
|
6159
6010
|
// src/controllers/channel-installations.ts
|
|
6160
|
-
import { randomUUID as
|
|
6011
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
6161
6012
|
function getTenantId11(request) {
|
|
6162
6013
|
const userTenantId = request.user?.tenantId;
|
|
6163
6014
|
if (userTenantId) {
|
|
@@ -6166,12 +6017,15 @@ function getTenantId11(request) {
|
|
|
6166
6017
|
return request.headers["x-tenant-id"] || "default";
|
|
6167
6018
|
}
|
|
6168
6019
|
async function getInstallationStore() {
|
|
6169
|
-
const {
|
|
6020
|
+
const { getStoreLattice: getStoreLattice16 } = await import("@axiom-lattice/core");
|
|
6021
|
+
const store = getStoreLattice16("default", "channelInstallation").store;
|
|
6022
|
+
if (store) return store;
|
|
6023
|
+
const { PostgreSQLChannelInstallationStore } = await import("@axiom-lattice/pg-stores");
|
|
6170
6024
|
const databaseUrl = process.env.DATABASE_URL;
|
|
6171
6025
|
if (!databaseUrl) {
|
|
6172
6026
|
throw new Error("DATABASE_URL is required for channel installation store");
|
|
6173
6027
|
}
|
|
6174
|
-
return new
|
|
6028
|
+
return new PostgreSQLChannelInstallationStore({
|
|
6175
6029
|
poolConfig: databaseUrl
|
|
6176
6030
|
});
|
|
6177
6031
|
}
|
|
@@ -6270,7 +6124,7 @@ async function createChannelInstallation(request, reply) {
|
|
|
6270
6124
|
}
|
|
6271
6125
|
}
|
|
6272
6126
|
const store = await getInstallationStore();
|
|
6273
|
-
const installationId = body.id ||
|
|
6127
|
+
const installationId = body.id || randomUUID6();
|
|
6274
6128
|
const installation = await store.createInstallation(
|
|
6275
6129
|
tenantId,
|
|
6276
6130
|
installationId,
|
|
@@ -6393,8 +6247,129 @@ function registerChannelInstallationRoutes(app2) {
|
|
|
6393
6247
|
app2.delete("/api/channel-installations/:installationId", deleteChannelInstallation);
|
|
6394
6248
|
}
|
|
6395
6249
|
|
|
6250
|
+
// src/bindings/index.ts
|
|
6251
|
+
var registryInstance = null;
|
|
6252
|
+
function setBindingRegistry(registry) {
|
|
6253
|
+
registryInstance = registry;
|
|
6254
|
+
}
|
|
6255
|
+
function getBindingRegistry() {
|
|
6256
|
+
if (!registryInstance) {
|
|
6257
|
+
throw new Error("BindingRegistry not initialized. Call setBindingRegistry() first.");
|
|
6258
|
+
}
|
|
6259
|
+
return registryInstance;
|
|
6260
|
+
}
|
|
6261
|
+
|
|
6262
|
+
// src/controllers/channel-bindings.ts
|
|
6263
|
+
function getTenantId12(request) {
|
|
6264
|
+
const userTenantId = request.user?.tenantId;
|
|
6265
|
+
if (userTenantId) return userTenantId;
|
|
6266
|
+
return request.headers["x-tenant-id"] || "default";
|
|
6267
|
+
}
|
|
6268
|
+
async function getBindingList(request, _reply) {
|
|
6269
|
+
const tenantId = getTenantId12(request);
|
|
6270
|
+
const { channel, agentId, channelInstallationId, limit, offset } = request.query;
|
|
6271
|
+
try {
|
|
6272
|
+
const registry = getBindingRegistry();
|
|
6273
|
+
const bindings = await registry.list({ channel, agentId, tenantId, channelInstallationId, limit, offset });
|
|
6274
|
+
return { success: true, message: "Bindings retrieved", data: { records: bindings, total: bindings.length } };
|
|
6275
|
+
} catch (error) {
|
|
6276
|
+
console.error("Failed to get bindings:", error);
|
|
6277
|
+
return { success: false, message: "Failed to retrieve bindings", data: { records: [], total: 0 } };
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
async function getBinding(request, reply) {
|
|
6281
|
+
const tenantId = getTenantId12(request);
|
|
6282
|
+
try {
|
|
6283
|
+
const registry = getBindingRegistry();
|
|
6284
|
+
const bindings = await registry.list({ tenantId });
|
|
6285
|
+
const binding = bindings.find((b) => b.id === request.params.id);
|
|
6286
|
+
if (!binding || binding.tenantId !== tenantId) {
|
|
6287
|
+
reply.status(404);
|
|
6288
|
+
return { success: false, message: "Binding not found" };
|
|
6289
|
+
}
|
|
6290
|
+
return { success: true, message: "Binding retrieved", data: binding };
|
|
6291
|
+
} catch (error) {
|
|
6292
|
+
console.error("Failed to get binding:", error);
|
|
6293
|
+
return { success: false, message: "Failed to retrieve binding" };
|
|
6294
|
+
}
|
|
6295
|
+
}
|
|
6296
|
+
async function createBinding(request, reply) {
|
|
6297
|
+
const tenantId = getTenantId12(request);
|
|
6298
|
+
try {
|
|
6299
|
+
const registry = getBindingRegistry();
|
|
6300
|
+
const binding = await registry.create({ ...request.body, tenantId });
|
|
6301
|
+
reply.status(201);
|
|
6302
|
+
return { success: true, message: "Binding created", data: binding };
|
|
6303
|
+
} catch (error) {
|
|
6304
|
+
console.error("Failed to create binding:", error);
|
|
6305
|
+
reply.status(500);
|
|
6306
|
+
return { success: false, message: "Failed to create binding" };
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
async function updateBinding(request, reply) {
|
|
6310
|
+
try {
|
|
6311
|
+
const tenantId = getTenantId12(request);
|
|
6312
|
+
const registry = getBindingRegistry();
|
|
6313
|
+
const bindings = await registry.list({ tenantId });
|
|
6314
|
+
const existing = bindings.find((b) => b.id === request.params.id);
|
|
6315
|
+
if (!existing || existing.tenantId !== tenantId) {
|
|
6316
|
+
reply.status(404);
|
|
6317
|
+
return { success: false, message: "Binding not found" };
|
|
6318
|
+
}
|
|
6319
|
+
const binding = await registry.update(request.params.id, request.body);
|
|
6320
|
+
return { success: true, message: "Binding updated", data: binding };
|
|
6321
|
+
} catch (error) {
|
|
6322
|
+
console.error("Failed to update binding:", error);
|
|
6323
|
+
reply.status(500);
|
|
6324
|
+
return { success: false, message: "Failed to update binding" };
|
|
6325
|
+
}
|
|
6326
|
+
}
|
|
6327
|
+
async function deleteBinding(request, reply) {
|
|
6328
|
+
try {
|
|
6329
|
+
const tenantId = getTenantId12(request);
|
|
6330
|
+
const registry = getBindingRegistry();
|
|
6331
|
+
const bindings = await registry.list({ tenantId });
|
|
6332
|
+
const existing = bindings.find((b) => b.id === request.params.id);
|
|
6333
|
+
if (!existing || existing.tenantId !== tenantId) {
|
|
6334
|
+
reply.status(404);
|
|
6335
|
+
return { success: false, message: "Binding not found" };
|
|
6336
|
+
}
|
|
6337
|
+
await registry.delete(request.params.id);
|
|
6338
|
+
return { success: true, message: "Binding deleted" };
|
|
6339
|
+
} catch (error) {
|
|
6340
|
+
console.error("Failed to delete binding:", error);
|
|
6341
|
+
reply.status(500);
|
|
6342
|
+
return { success: false, message: "Failed to delete binding" };
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
async function resolveBinding(request, _reply) {
|
|
6346
|
+
const tenantId = getTenantId12(request);
|
|
6347
|
+
const { channel, senderId, channelInstallationId } = request.query;
|
|
6348
|
+
try {
|
|
6349
|
+
const registry = getBindingRegistry();
|
|
6350
|
+
const binding = await registry.resolve({ channel, senderId, channelInstallationId, tenantId });
|
|
6351
|
+
if (!binding) {
|
|
6352
|
+
return { success: false, message: "No binding found", data: null };
|
|
6353
|
+
}
|
|
6354
|
+
return { success: true, message: "Binding found", data: binding };
|
|
6355
|
+
} catch (error) {
|
|
6356
|
+
console.error("Failed to resolve binding:", error);
|
|
6357
|
+
return { success: false, message: "Failed to resolve binding", data: null };
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6360
|
+
|
|
6361
|
+
// src/routes/channel-bindings.ts
|
|
6362
|
+
function registerChannelBindingRoutes(app2) {
|
|
6363
|
+
app2.get("/api/channel-bindings", getBindingList);
|
|
6364
|
+
app2.get("/api/channel-bindings/resolve", resolveBinding);
|
|
6365
|
+
app2.post("/api/channel-bindings", createBinding);
|
|
6366
|
+
app2.get("/api/channel-bindings/:id", getBinding);
|
|
6367
|
+
app2.put("/api/channel-bindings/:id", updateBinding);
|
|
6368
|
+
app2.delete("/api/channel-bindings/:id", deleteBinding);
|
|
6369
|
+
}
|
|
6370
|
+
|
|
6396
6371
|
// src/routes/index.ts
|
|
6397
|
-
var registerLatticeRoutes = (app2) => {
|
|
6372
|
+
var registerLatticeRoutes = (app2, channelDeps) => {
|
|
6398
6373
|
app2.post("/api/runs", createRun);
|
|
6399
6374
|
app2.post("/api/resume_stream", resumeStream);
|
|
6400
6375
|
app2.post("/api/assistants/:assistantId/threads/:threadId/abort", abortRun);
|
|
@@ -6529,8 +6504,46 @@ var registerLatticeRoutes = (app2) => {
|
|
|
6529
6504
|
autoApproveUsers: process.env.AUTO_APPROVE_USERS !== "false",
|
|
6530
6505
|
allowTenantRegistration: process.env.ALLOW_TENANT_REGISTRATION !== "false"
|
|
6531
6506
|
});
|
|
6532
|
-
registerChannelRoutes(app2);
|
|
6507
|
+
registerChannelRoutes(app2, channelDeps);
|
|
6533
6508
|
registerChannelInstallationRoutes(app2);
|
|
6509
|
+
if (channelDeps) {
|
|
6510
|
+
registerChannelBindingRoutes(app2);
|
|
6511
|
+
}
|
|
6512
|
+
if (channelDeps?.router) {
|
|
6513
|
+
app2.post("/api/channels/inbound", async (request, reply) => {
|
|
6514
|
+
try {
|
|
6515
|
+
const router = channelDeps.router;
|
|
6516
|
+
const msg = request.body;
|
|
6517
|
+
if (!msg.channel || !msg.sender || !msg.content) {
|
|
6518
|
+
reply.status(400).send({
|
|
6519
|
+
success: false,
|
|
6520
|
+
message: "Missing required fields: channel, sender, content"
|
|
6521
|
+
});
|
|
6522
|
+
return;
|
|
6523
|
+
}
|
|
6524
|
+
const inboundMessage = {
|
|
6525
|
+
channel: msg.channel,
|
|
6526
|
+
channelInstallationId: msg.channelInstallationId || "",
|
|
6527
|
+
tenantId: msg.tenantId || "default",
|
|
6528
|
+
sender: {
|
|
6529
|
+
id: msg.sender.id,
|
|
6530
|
+
displayName: msg.sender.displayName
|
|
6531
|
+
},
|
|
6532
|
+
content: {
|
|
6533
|
+
text: msg.content.text
|
|
6534
|
+
},
|
|
6535
|
+
replyTarget: msg.replyTarget
|
|
6536
|
+
};
|
|
6537
|
+
await router.dispatch(inboundMessage).catch((error) => {
|
|
6538
|
+
console.error("Inbound dispatch error:", error);
|
|
6539
|
+
});
|
|
6540
|
+
reply.status(200).send({ accepted: true });
|
|
6541
|
+
} catch (error) {
|
|
6542
|
+
console.error("Inbound route error:", error);
|
|
6543
|
+
reply.status(500).send({ success: false, message: "Internal error" });
|
|
6544
|
+
}
|
|
6545
|
+
});
|
|
6546
|
+
}
|
|
6534
6547
|
app2.get(
|
|
6535
6548
|
"/api/workflows/definitions",
|
|
6536
6549
|
getAllWorkflowDefinitions
|
|
@@ -6561,6 +6574,270 @@ var registerLatticeRoutes = (app2) => {
|
|
|
6561
6574
|
);
|
|
6562
6575
|
};
|
|
6563
6576
|
|
|
6577
|
+
// src/router/MessageRouter.ts
|
|
6578
|
+
import {
|
|
6579
|
+
getStoreLattice as getStoreLattice14,
|
|
6580
|
+
agentInstanceManager as agentInstanceManager6
|
|
6581
|
+
} from "@axiom-lattice/core";
|
|
6582
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
6583
|
+
var BindingNotFoundError = class extends Error {
|
|
6584
|
+
constructor(message) {
|
|
6585
|
+
super(message);
|
|
6586
|
+
this.name = "BindingNotFoundError";
|
|
6587
|
+
}
|
|
6588
|
+
};
|
|
6589
|
+
var MessageRouter = class {
|
|
6590
|
+
constructor(config) {
|
|
6591
|
+
this.middlewares = [...config.middlewares];
|
|
6592
|
+
this.bindingRegistry = config.bindingRegistry;
|
|
6593
|
+
this.adapterRegistry = config.adapterRegistry;
|
|
6594
|
+
this.installationStore = config.installationStore;
|
|
6595
|
+
}
|
|
6596
|
+
use(middleware) {
|
|
6597
|
+
this.middlewares.push(middleware);
|
|
6598
|
+
}
|
|
6599
|
+
async dispatch(message) {
|
|
6600
|
+
const ctx = {
|
|
6601
|
+
inboundMessage: message,
|
|
6602
|
+
metadata: {}
|
|
6603
|
+
};
|
|
6604
|
+
try {
|
|
6605
|
+
await this.runMiddlewares(ctx, async () => {
|
|
6606
|
+
let binding = await this.bindingRegistry.resolve({
|
|
6607
|
+
channel: message.channel,
|
|
6608
|
+
senderId: message.sender.id,
|
|
6609
|
+
channelInstallationId: message.channelInstallationId,
|
|
6610
|
+
tenantId: message.tenantId
|
|
6611
|
+
});
|
|
6612
|
+
if (!binding) {
|
|
6613
|
+
const installation = await this.installationStore.getInstallationById(
|
|
6614
|
+
message.channelInstallationId
|
|
6615
|
+
);
|
|
6616
|
+
if (installation?.rejectWhenNoBinding) {
|
|
6617
|
+
throw new BindingNotFoundError(
|
|
6618
|
+
`No binding for sender "${message.sender.id}" on channel "${message.channel}"`
|
|
6619
|
+
);
|
|
6620
|
+
}
|
|
6621
|
+
if (installation?.fallbackAgentId) {
|
|
6622
|
+
binding = {
|
|
6623
|
+
id: "fallback",
|
|
6624
|
+
channel: message.channel,
|
|
6625
|
+
channelInstallationId: message.channelInstallationId,
|
|
6626
|
+
tenantId: message.tenantId,
|
|
6627
|
+
senderId: message.sender.id,
|
|
6628
|
+
agentId: installation.fallbackAgentId,
|
|
6629
|
+
threadId: void 0,
|
|
6630
|
+
threadMode: "fixed",
|
|
6631
|
+
enabled: true,
|
|
6632
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
6633
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
6634
|
+
};
|
|
6635
|
+
} else {
|
|
6636
|
+
throw new BindingNotFoundError(
|
|
6637
|
+
`No binding for sender "${message.sender.id}" and no fallback configured`
|
|
6638
|
+
);
|
|
6639
|
+
}
|
|
6640
|
+
}
|
|
6641
|
+
ctx.binding = binding;
|
|
6642
|
+
if (!binding.enabled) {
|
|
6643
|
+
throw new BindingNotFoundError(
|
|
6644
|
+
`Binding for sender "${message.sender.id}" is disabled`
|
|
6645
|
+
);
|
|
6646
|
+
}
|
|
6647
|
+
let threadId = ctx.binding.threadId;
|
|
6648
|
+
if (!threadId) {
|
|
6649
|
+
const threadStore = getStoreLattice14("default", "thread").store;
|
|
6650
|
+
const newThreadId = randomUUID7();
|
|
6651
|
+
const newThread = await threadStore.createThread(
|
|
6652
|
+
message.tenantId,
|
|
6653
|
+
ctx.binding.agentId,
|
|
6654
|
+
newThreadId,
|
|
6655
|
+
{
|
|
6656
|
+
metadata: {
|
|
6657
|
+
channel: message.channel,
|
|
6658
|
+
channelInstallationId: message.channelInstallationId,
|
|
6659
|
+
senderId: message.sender.id,
|
|
6660
|
+
bindingId: ctx.binding.id
|
|
6661
|
+
}
|
|
6662
|
+
}
|
|
6663
|
+
);
|
|
6664
|
+
threadId = newThread.id;
|
|
6665
|
+
if (ctx.binding.id !== "fallback") {
|
|
6666
|
+
await this.bindingRegistry.update(ctx.binding.id, { threadId });
|
|
6667
|
+
ctx.binding.threadId = threadId;
|
|
6668
|
+
} else {
|
|
6669
|
+
ctx.binding.threadId = threadId;
|
|
6670
|
+
}
|
|
6671
|
+
}
|
|
6672
|
+
const agent = agentInstanceManager6.getAgent({
|
|
6673
|
+
tenant_id: message.tenantId,
|
|
6674
|
+
assistant_id: ctx.binding.agentId,
|
|
6675
|
+
thread_id: threadId,
|
|
6676
|
+
workspace_id: ctx.binding.workspaceId || "",
|
|
6677
|
+
project_id: ctx.binding.projectId || ""
|
|
6678
|
+
});
|
|
6679
|
+
const invokeResult = await agent.invoke({
|
|
6680
|
+
input: { message: message.content.text }
|
|
6681
|
+
});
|
|
6682
|
+
ctx.result = extractTextFromInvokeResult(invokeResult);
|
|
6683
|
+
if (message.replyTarget) {
|
|
6684
|
+
const adapter = this.adapterRegistry.get(message.replyTarget.adapterChannel);
|
|
6685
|
+
if (adapter) {
|
|
6686
|
+
const installation = await this.installationStore.getInstallationById(
|
|
6687
|
+
message.channelInstallationId
|
|
6688
|
+
);
|
|
6689
|
+
if (installation) {
|
|
6690
|
+
await adapter.sendReply(
|
|
6691
|
+
message.replyTarget,
|
|
6692
|
+
{ text: ctx.result },
|
|
6693
|
+
installation
|
|
6694
|
+
);
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
});
|
|
6699
|
+
return {
|
|
6700
|
+
success: true,
|
|
6701
|
+
bindingId: ctx.binding?.id,
|
|
6702
|
+
threadId: ctx.binding?.threadId,
|
|
6703
|
+
result: ctx.result
|
|
6704
|
+
};
|
|
6705
|
+
} catch (error) {
|
|
6706
|
+
ctx.error = error instanceof Error ? error : new Error(String(error));
|
|
6707
|
+
return {
|
|
6708
|
+
success: false,
|
|
6709
|
+
bindingId: ctx.binding?.id,
|
|
6710
|
+
threadId: ctx.binding?.threadId,
|
|
6711
|
+
error: ctx.error
|
|
6712
|
+
};
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6715
|
+
async runMiddlewares(ctx, finalHandler) {
|
|
6716
|
+
const dispatch = (index) => {
|
|
6717
|
+
if (index >= this.middlewares.length) {
|
|
6718
|
+
return finalHandler();
|
|
6719
|
+
}
|
|
6720
|
+
const middleware = this.middlewares[index];
|
|
6721
|
+
return middleware(ctx, () => dispatch(index + 1));
|
|
6722
|
+
};
|
|
6723
|
+
return dispatch(0);
|
|
6724
|
+
}
|
|
6725
|
+
};
|
|
6726
|
+
function extractTextFromInvokeResult(result) {
|
|
6727
|
+
if (result && typeof result === "object" && "messages" in result) {
|
|
6728
|
+
const messages = result.messages;
|
|
6729
|
+
if (Array.isArray(messages)) {
|
|
6730
|
+
const aiMessages = messages.filter((m) => m.role === "ai");
|
|
6731
|
+
if (aiMessages.length > 0) {
|
|
6732
|
+
return aiMessages.map((m) => m.content).join("\n");
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
return JSON.stringify(result);
|
|
6737
|
+
}
|
|
6738
|
+
|
|
6739
|
+
// src/channels/registry.ts
|
|
6740
|
+
var ChannelAdapterRegistry = class {
|
|
6741
|
+
constructor() {
|
|
6742
|
+
this.adapters = /* @__PURE__ */ new Map();
|
|
6743
|
+
}
|
|
6744
|
+
register(adapter) {
|
|
6745
|
+
if (this.adapters.has(adapter.channel)) {
|
|
6746
|
+
throw new Error(`Channel adapter "${adapter.channel}" already registered`);
|
|
6747
|
+
}
|
|
6748
|
+
this.adapters.set(adapter.channel, adapter);
|
|
6749
|
+
}
|
|
6750
|
+
get(channel) {
|
|
6751
|
+
return this.adapters.get(channel);
|
|
6752
|
+
}
|
|
6753
|
+
list() {
|
|
6754
|
+
return Array.from(this.adapters.keys());
|
|
6755
|
+
}
|
|
6756
|
+
};
|
|
6757
|
+
|
|
6758
|
+
// src/router/middlewares/deduplication.ts
|
|
6759
|
+
var processedMessages = /* @__PURE__ */ new Map();
|
|
6760
|
+
function createDeduplicationMiddleware(ttlMs = 5 * 60 * 1e3) {
|
|
6761
|
+
return async (ctx, next) => {
|
|
6762
|
+
const msg = ctx.inboundMessage;
|
|
6763
|
+
const msgId = msg.content.metadata?.messageId;
|
|
6764
|
+
const key = msgId ? `${msg.channel}:${msg.channelInstallationId}:${msgId}` : `${msg.channel}:${msg.channelInstallationId}:${msg.sender.id}`;
|
|
6765
|
+
const now = Date.now();
|
|
6766
|
+
const lastProcessed = processedMessages.get(key);
|
|
6767
|
+
if (lastProcessed && now - lastProcessed < ttlMs) return;
|
|
6768
|
+
processedMessages.set(key, now);
|
|
6769
|
+
if (processedMessages.size > 1e4) {
|
|
6770
|
+
const oldest = Array.from(processedMessages.entries()).sort((a, b) => a[1] - b[1])[0];
|
|
6771
|
+
if (oldest) processedMessages.delete(oldest[0]);
|
|
6772
|
+
}
|
|
6773
|
+
await next();
|
|
6774
|
+
};
|
|
6775
|
+
}
|
|
6776
|
+
|
|
6777
|
+
// src/router/middlewares/rateLimit.ts
|
|
6778
|
+
var RateLimitError = class extends Error {
|
|
6779
|
+
constructor(message) {
|
|
6780
|
+
super(message);
|
|
6781
|
+
this.name = "RateLimitError";
|
|
6782
|
+
}
|
|
6783
|
+
};
|
|
6784
|
+
var rateCounters = /* @__PURE__ */ new Map();
|
|
6785
|
+
function createRateLimitMiddleware(maxRequests = 10, windowMs = 60 * 1e3, maxEntries = 1e4) {
|
|
6786
|
+
return async (ctx, next) => {
|
|
6787
|
+
const senderKey = `${ctx.inboundMessage.channel}:${ctx.inboundMessage.sender.id}`;
|
|
6788
|
+
const now = Date.now();
|
|
6789
|
+
let counter = rateCounters.get(senderKey);
|
|
6790
|
+
if (!counter || now > counter.resetAt) {
|
|
6791
|
+
counter = { count: 0, resetAt: now + windowMs };
|
|
6792
|
+
}
|
|
6793
|
+
counter.count++;
|
|
6794
|
+
rateCounters.set(senderKey, counter);
|
|
6795
|
+
if (rateCounters.size > maxEntries) {
|
|
6796
|
+
const oldest = Array.from(rateCounters.entries()).sort((a, b) => a[1].resetAt - b[1].resetAt)[0];
|
|
6797
|
+
if (oldest) rateCounters.delete(oldest[0]);
|
|
6798
|
+
}
|
|
6799
|
+
if (counter.count > maxRequests) {
|
|
6800
|
+
throw new RateLimitError(`Rate limit exceeded`);
|
|
6801
|
+
}
|
|
6802
|
+
await next();
|
|
6803
|
+
};
|
|
6804
|
+
}
|
|
6805
|
+
|
|
6806
|
+
// src/router/middlewares/auditLogger.ts
|
|
6807
|
+
var logger2 = new Logger({ serviceName: "lattice/gateway/audit" });
|
|
6808
|
+
function createAuditLoggerMiddleware() {
|
|
6809
|
+
return async (ctx, next) => {
|
|
6810
|
+
const start2 = Date.now();
|
|
6811
|
+
try {
|
|
6812
|
+
await next();
|
|
6813
|
+
logger2.info("message routed", {
|
|
6814
|
+
event: "message:routed",
|
|
6815
|
+
channel: ctx.inboundMessage.channel,
|
|
6816
|
+
senderId: ctx.inboundMessage.sender.id,
|
|
6817
|
+
agentId: ctx.binding?.agentId,
|
|
6818
|
+
threadId: ctx.binding?.threadId,
|
|
6819
|
+
duration: Date.now() - start2,
|
|
6820
|
+
status: "success"
|
|
6821
|
+
});
|
|
6822
|
+
} catch (error) {
|
|
6823
|
+
logger2.error(
|
|
6824
|
+
error instanceof Error ? error.message : String(error),
|
|
6825
|
+
{
|
|
6826
|
+
event: "message:error",
|
|
6827
|
+
channel: ctx.inboundMessage.channel,
|
|
6828
|
+
senderId: ctx.inboundMessage.sender.id,
|
|
6829
|
+
duration: Date.now() - start2,
|
|
6830
|
+
status: "error"
|
|
6831
|
+
}
|
|
6832
|
+
);
|
|
6833
|
+
throw error;
|
|
6834
|
+
}
|
|
6835
|
+
};
|
|
6836
|
+
}
|
|
6837
|
+
|
|
6838
|
+
// src/index.ts
|
|
6839
|
+
import { setBindingRegistry as setCoreBindingRegistry } from "@axiom-lattice/core";
|
|
6840
|
+
|
|
6564
6841
|
// src/swagger.ts
|
|
6565
6842
|
import swagger from "@fastify/swagger";
|
|
6566
6843
|
import swaggerUi from "@fastify/swagger-ui";
|
|
@@ -6906,7 +7183,7 @@ var DEFAULT_LOGGER_CONFIG = {
|
|
|
6906
7183
|
loggerName: "lattice/gateway"
|
|
6907
7184
|
};
|
|
6908
7185
|
var loggerLattice = initializeLogger(DEFAULT_LOGGER_CONFIG);
|
|
6909
|
-
var
|
|
7186
|
+
var logger3 = loggerLattice.client;
|
|
6910
7187
|
function initializeLogger(config) {
|
|
6911
7188
|
if (loggerLatticeManager.hasLattice("default")) {
|
|
6912
7189
|
loggerLatticeManager.removeLattice("default");
|
|
@@ -6999,7 +7276,7 @@ app.setErrorHandler((error, request, reply) => {
|
|
|
6999
7276
|
"x-request-id": getHeaderValue(request.headers["x-request-id"]),
|
|
7000
7277
|
"x-user-id": getHeaderValue(request.headers["x-user-id"])
|
|
7001
7278
|
};
|
|
7002
|
-
|
|
7279
|
+
logger3.error(
|
|
7003
7280
|
`\u8BF7\u6C42\u9519\u8BEF: ${request.method} ${request.url} error:${error.message}`,
|
|
7004
7281
|
{
|
|
7005
7282
|
...context,
|
|
@@ -7039,29 +7316,51 @@ var start = async (config) => {
|
|
|
7039
7316
|
file: config.loggerConfig.file || DEFAULT_LOGGER_CONFIG.file
|
|
7040
7317
|
};
|
|
7041
7318
|
loggerLattice = initializeLogger(loggerConfig);
|
|
7042
|
-
|
|
7319
|
+
logger3 = loggerLattice.client;
|
|
7043
7320
|
}
|
|
7044
7321
|
app.decorate("loggerLattice", loggerLattice);
|
|
7045
|
-
|
|
7322
|
+
let channelDeps;
|
|
7323
|
+
try {
|
|
7324
|
+
const { getStoreLattice: getStoreLattice16 } = await import("@axiom-lattice/core");
|
|
7325
|
+
const bindingStore = getStoreLattice16("default", "channelBinding").store;
|
|
7326
|
+
const installationStore = getStoreLattice16("default", "channelInstallation").store;
|
|
7327
|
+
setBindingRegistry(bindingStore);
|
|
7328
|
+
setCoreBindingRegistry(bindingStore);
|
|
7329
|
+
const adapterRegistry = new ChannelAdapterRegistry();
|
|
7330
|
+
adapterRegistry.register(larkChannelAdapter);
|
|
7331
|
+
const router = new MessageRouter({
|
|
7332
|
+
middlewares: [
|
|
7333
|
+
createDeduplicationMiddleware(),
|
|
7334
|
+
createRateLimitMiddleware(),
|
|
7335
|
+
createAuditLoggerMiddleware()
|
|
7336
|
+
],
|
|
7337
|
+
bindingRegistry: bindingStore,
|
|
7338
|
+
adapterRegistry,
|
|
7339
|
+
installationStore
|
|
7340
|
+
});
|
|
7341
|
+
channelDeps = { router, installationStore };
|
|
7342
|
+
} catch {
|
|
7343
|
+
}
|
|
7344
|
+
registerLatticeRoutes(app, channelDeps);
|
|
7046
7345
|
try {
|
|
7047
7346
|
const storeLattice = getStoreLattice15("default", "database");
|
|
7048
7347
|
const store = storeLattice.store;
|
|
7049
7348
|
sqlDatabaseManager2.setConfigStore(store);
|
|
7050
|
-
|
|
7349
|
+
logger3.info("Database config store set for SqlDatabaseManager");
|
|
7051
7350
|
} catch (error) {
|
|
7052
|
-
|
|
7351
|
+
logger3.warn("Failed to set database config store: " + (error instanceof Error ? error.message : String(error)));
|
|
7053
7352
|
}
|
|
7054
7353
|
if (!sandboxLatticeManager2.hasLattice("default")) {
|
|
7055
7354
|
sandboxLatticeManager2.registerLattice("default", getConfiguredSandboxProvider());
|
|
7056
|
-
|
|
7355
|
+
logger3.info("Registered sandbox manager from env configuration");
|
|
7057
7356
|
}
|
|
7058
7357
|
const target_port = config?.port || Number(process.env.PORT) || 4001;
|
|
7059
7358
|
await app.listen({ port: target_port, host: "0.0.0.0" });
|
|
7060
|
-
|
|
7359
|
+
logger3.info(`Lattice Gateway is running on port: ${target_port}`);
|
|
7061
7360
|
try {
|
|
7062
|
-
|
|
7361
|
+
logger3.info("AgentLifecycleManager initialized");
|
|
7063
7362
|
} catch (error) {
|
|
7064
|
-
|
|
7363
|
+
logger3.warn("Failed to initialize AgentLifecycleManager", { error });
|
|
7065
7364
|
}
|
|
7066
7365
|
const queueServiceConfig = config?.queueServiceConfig;
|
|
7067
7366
|
if (queueServiceConfig) {
|
|
@@ -7072,14 +7371,14 @@ var start = async (config) => {
|
|
|
7072
7371
|
}
|
|
7073
7372
|
}
|
|
7074
7373
|
try {
|
|
7075
|
-
|
|
7374
|
+
logger3.info("Starting agent instance recovery...");
|
|
7076
7375
|
const restoreStats = await agentInstanceManager8.restore();
|
|
7077
|
-
|
|
7376
|
+
logger3.info(`Agent recovery complete: ${restoreStats.restored} threads restored, ${restoreStats.errors} errors`);
|
|
7078
7377
|
} catch (error) {
|
|
7079
|
-
|
|
7378
|
+
logger3.error("Agent recovery failed", { error });
|
|
7080
7379
|
}
|
|
7081
7380
|
} catch (err) {
|
|
7082
|
-
|
|
7381
|
+
logger3.error("Server start failed", { error: err });
|
|
7083
7382
|
process.exit(1);
|
|
7084
7383
|
}
|
|
7085
7384
|
};
|