@axiom-lattice/gateway 3.0.2 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +37 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +37 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- package/.eslintrc.json +0 -22
- package/.turbo/turbo-build.log +0 -46
- package/AGENTS.md +0 -112
- package/CHANGELOG.md +0 -1190
- package/LOGGER_CONFIG.md +0 -135
- package/RESUME_STREAM_CONTENT_BASED.md +0 -326
- package/RESUME_STREAM_README.md +0 -388
- package/jest.config.js +0 -72
- package/public/sdk/README.md +0 -695
- package/public/sdk/dashboard-engine-skill.md +0 -1122
- package/public/sdk/dashboard-single-file-spec.md +0 -357
- package/public/sdk/data-query-sdk-skill.md +0 -307
- package/public/sdk/data-query-sdk.d.ts +0 -252
- package/public/sdk/data-query-sdk.js +0 -970
- package/public/sdk/occupancy-dashboard.html +0 -363
- package/public/sdk/test-dashboard.html +0 -690
- package/scripts/seed-lark-installation.ts +0 -54
- package/src/__tests__/__mocks__/e2b.ts +0 -1
- package/src/__tests__/a2a.test.ts +0 -428
- package/src/__tests__/channel-installations.test.ts +0 -199
- package/src/__tests__/data-query.test.ts +0 -77
- package/src/__tests__/sandbox-provider-registration.test.ts +0 -74
- package/src/__tests__/setup.ts +0 -5
- package/src/__tests__/workspace.test.ts +0 -400
- package/src/channels/__tests__/routes.test.ts +0 -50
- package/src/channels/lark/LarkChannelAdapter.ts +0 -217
- package/src/channels/lark/README.md +0 -187
- package/src/channels/lark/__tests__/controller.test.ts +0 -136
- package/src/channels/lark/__tests__/mapping-service.test.ts +0 -118
- package/src/channels/lark/__tests__/parser.test.ts +0 -72
- package/src/channels/lark/__tests__/sender.test.ts +0 -37
- package/src/channels/lark/__tests__/verification.test.ts +0 -157
- package/src/channels/lark/controller.ts +0 -48
- package/src/channels/lark/mapping-service.ts +0 -138
- package/src/channels/lark/parser.ts +0 -68
- package/src/channels/lark/routes.ts +0 -22
- package/src/channels/lark/sender.ts +0 -58
- package/src/channels/lark/types.ts +0 -20
- package/src/channels/lark/verification.ts +0 -32
- package/src/channels/registry.ts +0 -20
- package/src/channels/routes.ts +0 -30
- package/src/channels/wechat/WechatChannelAdapter.ts +0 -294
- package/src/channels/wechat/__tests__/context-store.test.ts +0 -148
- package/src/channels/wechat/context-store.ts +0 -54
- package/src/channels/wechat/controller.ts +0 -59
- package/src/channels/wechat/routes.ts +0 -7
- package/src/channels/wechat/types.ts +0 -62
- package/src/channels/wechat/wechat-client.ts +0 -162
- package/src/config/local-a2a-templates.ts +0 -59
- package/src/config.ts +0 -124
- package/src/controllers/__tests__/assistant-local-a2a.test.ts +0 -147
- package/src/controllers/__tests__/local-a2a-templates.test.ts +0 -112
- package/src/controllers/__tests__/mime.test.ts +0 -74
- package/src/controllers/__tests__/resources.test.ts +0 -122
- package/src/controllers/__tests__/run.test.ts +0 -115
- package/src/controllers/__tests__/tasks.test.ts +0 -215
- package/src/controllers/__tests__/workflow-tracking.test.ts +0 -113
- package/src/controllers/agent_task.ts +0 -72
- package/src/controllers/assistant.ts +0 -490
- package/src/controllers/auth.ts +0 -524
- package/src/controllers/channel-bindings.ts +0 -135
- package/src/controllers/channel-installations.ts +0 -409
- package/src/controllers/collections.ts +0 -176
- package/src/controllers/config.ts +0 -126
- package/src/controllers/connections.ts +0 -181
- package/src/controllers/data-query.ts +0 -236
- package/src/controllers/database-configs.ts +0 -440
- package/src/controllers/embeddings.ts +0 -22
- package/src/controllers/entries.ts +0 -167
- package/src/controllers/eval.ts +0 -513
- package/src/controllers/export-import.ts +0 -166
- package/src/controllers/health.ts +0 -36
- package/src/controllers/local-a2a-templates.ts +0 -18
- package/src/controllers/local-a2a.ts +0 -149
- package/src/controllers/mcp-configs.ts +0 -673
- package/src/controllers/memory.ts +0 -257
- package/src/controllers/menu-items.ts +0 -114
- package/src/controllers/metrics-configs.ts +0 -1061
- package/src/controllers/middleware-types.ts +0 -15
- package/src/controllers/models.ts +0 -199
- package/src/controllers/personal-assistant.ts +0 -191
- package/src/controllers/resources.ts +0 -477
- package/src/controllers/run.ts +0 -338
- package/src/controllers/sandbox.ts +0 -255
- package/src/controllers/schedules.ts +0 -336
- package/src/controllers/skills.ts +0 -256
- package/src/controllers/tasks.ts +0 -213
- package/src/controllers/tenants.ts +0 -121
- package/src/controllers/thread_status.ts +0 -66
- package/src/controllers/threads.ts +0 -260
- package/src/controllers/tools.ts +0 -139
- package/src/controllers/users.ts +0 -135
- package/src/controllers/workflow-tracking.ts +0 -693
- package/src/controllers/workspace.ts +0 -879
- package/src/export_registrations/a2a-api-key.registration.ts +0 -89
- package/src/export_registrations/agent.registration.ts +0 -80
- package/src/export_registrations/binding.registration.ts +0 -106
- package/src/export_registrations/channel-installation.registration.ts +0 -88
- package/src/export_registrations/collection.registration.ts +0 -85
- package/src/export_registrations/connection.registration.ts +0 -97
- package/src/export_registrations/database-config.registration.ts +0 -94
- package/src/export_registrations/eval.registration.ts +0 -334
- package/src/export_registrations/index.ts +0 -31
- package/src/export_registrations/mcp-config.registration.ts +0 -97
- package/src/export_registrations/menu.registration.ts +0 -91
- package/src/export_registrations/metrics-config.registration.ts +0 -94
- package/src/export_registrations/skill.registration.ts +0 -88
- package/src/export_registrations/task.registration.ts +0 -99
- package/src/index.ts +0 -497
- package/src/logger/Logger.ts +0 -186
- package/src/router/MessageContext.ts +0 -14
- package/src/router/MessageRouter.ts +0 -490
- package/src/router/middlewares/auditLogger.ts +0 -34
- package/src/router/middlewares/deduplication.ts +0 -38
- package/src/router/middlewares/index.ts +0 -3
- package/src/router/middlewares/rateLimit.ts +0 -46
- package/src/routes/__tests__/a2a-test-connection.test.ts +0 -177
- package/src/routes/a2a-bridge.ts +0 -266
- package/src/routes/a2a-test-connection.ts +0 -126
- package/src/routes/a2a.ts +0 -851
- package/src/routes/channel-bindings.ts +0 -11
- package/src/routes/channel-installations.ts +0 -33
- package/src/routes/index.ts +0 -640
- package/src/routes/menu-items.ts +0 -10
- package/src/routes/resource-routes.ts +0 -35
- package/src/routes/stt.ts +0 -102
- package/src/schemas/data-query.ts +0 -69
- package/src/schemas/index.ts +0 -852
- package/src/services/ExportImportService.ts +0 -296
- package/src/services/__tests__/ExportImportService.test.ts +0 -130
- package/src/services/__tests__/eval-runner.test.ts +0 -131
- package/src/services/__tests__/local-a2a.test.ts +0 -124
- package/src/services/agent_task_consumer.ts +0 -348
- package/src/services/collection_service.ts +0 -258
- package/src/services/eval-runner.ts +0 -283
- package/src/services/local-a2a.ts +0 -354
- package/src/services/queue_service.ts +0 -104
- package/src/services/sandbox_service.ts +0 -223
- package/src/services/skill_service.ts +0 -97
- package/src/services/supabase.ts +0 -60
- package/src/swagger.ts +0 -77
- package/src/types/index.ts +0 -118
- package/src/types/koa.d.ts +0 -7
- package/src/utils/__tests__/local-a2a-runtime.test.ts +0 -77
- package/src/utils/__tests__/path-containment.test.ts +0 -35
- package/src/utils/a2a-url.ts +0 -20
- package/src/utils/local-a2a-runtime.ts +0 -107
- package/src/utils/mime.ts +0 -44
- package/src/utils/path-containment.ts +0 -38
- package/tsconfig.json +0 -26
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { parseLarkMessageEvent } from "../parser";
|
|
2
|
-
|
|
3
|
-
describe("parseLarkMessageEvent", () => {
|
|
4
|
-
it("parses a direct text message event", () => {
|
|
5
|
-
const parsed = parseLarkMessageEvent({
|
|
6
|
-
header: { event_type: "im.message.receive_v1" },
|
|
7
|
-
event: {
|
|
8
|
-
sender: { sender_id: { open_id: "ou_user_1" } },
|
|
9
|
-
message: {
|
|
10
|
-
message_id: "om_1",
|
|
11
|
-
chat_id: "oc_chat_1",
|
|
12
|
-
chat_type: "p2p",
|
|
13
|
-
message_type: "text",
|
|
14
|
-
content: '{"text":"hello"}',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
expect(parsed).toEqual({
|
|
20
|
-
messageId: "om_1",
|
|
21
|
-
openId: "ou_user_1",
|
|
22
|
-
chatId: "oc_chat_1",
|
|
23
|
-
chatType: "direct",
|
|
24
|
-
text: "hello",
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("parses a group text message event", () => {
|
|
29
|
-
const parsed = parseLarkMessageEvent({
|
|
30
|
-
header: { event_type: "im.message.receive_v1" },
|
|
31
|
-
event: {
|
|
32
|
-
sender: { sender_id: { open_id: "ou_user_2" } },
|
|
33
|
-
message: {
|
|
34
|
-
message_id: "om_2",
|
|
35
|
-
chat_id: "oc_group_1",
|
|
36
|
-
chat_type: "group",
|
|
37
|
-
message_type: "text",
|
|
38
|
-
content: '{"text":"team update"}',
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
expect(parsed.chatType).toBe("group");
|
|
44
|
-
expect(parsed.text).toBe("team update");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("returns null for unsupported event types", () => {
|
|
48
|
-
const parsed = parseLarkMessageEvent({
|
|
49
|
-
header: { event_type: "im.message.read_v1" },
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
expect(parsed).toBeNull();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("returns null for non-text messages", () => {
|
|
56
|
-
const parsed = parseLarkMessageEvent({
|
|
57
|
-
header: { event_type: "im.message.receive_v1" },
|
|
58
|
-
event: {
|
|
59
|
-
sender: { sender_id: { open_id: "ou_user_3" } },
|
|
60
|
-
message: {
|
|
61
|
-
message_id: "om_3",
|
|
62
|
-
chat_id: "oc_chat_3",
|
|
63
|
-
chat_type: "p2p",
|
|
64
|
-
message_type: "image",
|
|
65
|
-
content: '{"image_key":"img_1"}',
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
expect(parsed).toBeNull();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { createLarkSender } from "../sender";
|
|
2
|
-
|
|
3
|
-
describe("createLarkSender", () => {
|
|
4
|
-
it("sends a text reply through the lark sdk client", async () => {
|
|
5
|
-
const createMock = jest.fn().mockResolvedValue({ code: 0 });
|
|
6
|
-
const sdkClient = {
|
|
7
|
-
im: {
|
|
8
|
-
v1: {
|
|
9
|
-
message: {
|
|
10
|
-
create: createMock,
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const sender = await createLarkSender(
|
|
17
|
-
{
|
|
18
|
-
appId: "cli_app_1",
|
|
19
|
-
appSecret: "secret",
|
|
20
|
-
},
|
|
21
|
-
sdkClient as never,
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
await sender.sendTextReply({ chatId: "oc_1", text: "hello" });
|
|
25
|
-
|
|
26
|
-
expect(createMock).toHaveBeenCalledWith({
|
|
27
|
-
params: {
|
|
28
|
-
receive_id_type: "chat_id",
|
|
29
|
-
},
|
|
30
|
-
data: {
|
|
31
|
-
receive_id: "oc_1",
|
|
32
|
-
msg_type: "text",
|
|
33
|
-
content: JSON.stringify({ text: "hello" }),
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
});
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import type { FastifyRequest } from "fastify";
|
|
2
|
-
import crypto from "crypto";
|
|
3
|
-
import {
|
|
4
|
-
createLarkRequestVerifier,
|
|
5
|
-
decryptLarkPayload,
|
|
6
|
-
parseLarkRequestBody,
|
|
7
|
-
} from "../verification";
|
|
8
|
-
|
|
9
|
-
describe("createLarkRequestVerifier", () => {
|
|
10
|
-
it("accepts requests with the configured verification token", () => {
|
|
11
|
-
const verify = createLarkRequestVerifier({
|
|
12
|
-
enabled: true,
|
|
13
|
-
appId: "app-id",
|
|
14
|
-
appSecret: "app-secret",
|
|
15
|
-
verificationToken: "token-1",
|
|
16
|
-
tenantId: "tenant-a",
|
|
17
|
-
assistantId: "assistant-a",
|
|
18
|
-
mappingMode: "hybrid",
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const request = {
|
|
22
|
-
headers: {},
|
|
23
|
-
body: { token: "token-1" },
|
|
24
|
-
} as FastifyRequest;
|
|
25
|
-
|
|
26
|
-
expect(verify(request)).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("rejects requests with the wrong verification token", () => {
|
|
30
|
-
const verify = createLarkRequestVerifier({
|
|
31
|
-
enabled: true,
|
|
32
|
-
appId: "app-id",
|
|
33
|
-
appSecret: "app-secret",
|
|
34
|
-
verificationToken: "token-1",
|
|
35
|
-
tenantId: "tenant-a",
|
|
36
|
-
assistantId: "assistant-a",
|
|
37
|
-
mappingMode: "hybrid",
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const request = {
|
|
41
|
-
headers: {},
|
|
42
|
-
body: { token: "token-2" },
|
|
43
|
-
} as FastifyRequest;
|
|
44
|
-
|
|
45
|
-
expect(verify(request)).toBe(false);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("accepts requests carrying lark signature headers", () => {
|
|
49
|
-
const verify = createLarkRequestVerifier({
|
|
50
|
-
enabled: true,
|
|
51
|
-
appId: "app-id",
|
|
52
|
-
appSecret: "app-secret",
|
|
53
|
-
tenantId: "tenant-a",
|
|
54
|
-
assistantId: "assistant-a",
|
|
55
|
-
mappingMode: "hybrid",
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const request = {
|
|
59
|
-
headers: {
|
|
60
|
-
"x-lark-request-timestamp": "1712730010",
|
|
61
|
-
"x-lark-signature": "signature",
|
|
62
|
-
},
|
|
63
|
-
body: {},
|
|
64
|
-
} as FastifyRequest;
|
|
65
|
-
|
|
66
|
-
expect(verify(request)).toBe(true);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("accepts v2 payloads that carry the token in header.token", () => {
|
|
70
|
-
const verify = createLarkRequestVerifier({
|
|
71
|
-
enabled: true,
|
|
72
|
-
appId: "app-id",
|
|
73
|
-
appSecret: "app-secret",
|
|
74
|
-
verificationToken: "token-1",
|
|
75
|
-
tenantId: "tenant-a",
|
|
76
|
-
assistantId: "assistant-a",
|
|
77
|
-
mappingMode: "hybrid",
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const request = {
|
|
81
|
-
headers: {},
|
|
82
|
-
body: {
|
|
83
|
-
schema: "2.0",
|
|
84
|
-
header: {
|
|
85
|
-
token: "token-1",
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
} as FastifyRequest;
|
|
89
|
-
|
|
90
|
-
expect(verify(request)).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("decrypts an encrypted lark payload", () => {
|
|
94
|
-
const encryptKey = "encrypt-key-1";
|
|
95
|
-
const payload = JSON.stringify({
|
|
96
|
-
type: "url_verification",
|
|
97
|
-
challenge: "challenge-1",
|
|
98
|
-
token: "token-1",
|
|
99
|
-
});
|
|
100
|
-
const encrypted = encryptForTest(encryptKey, payload);
|
|
101
|
-
|
|
102
|
-
expect(decryptLarkPayload(encryptKey, encrypted)).toEqual({
|
|
103
|
-
type: "url_verification",
|
|
104
|
-
challenge: "challenge-1",
|
|
105
|
-
token: "token-1",
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("parses encrypted payloads before token verification", () => {
|
|
110
|
-
const encryptKey = "encrypt-key-1";
|
|
111
|
-
const verify = createLarkRequestVerifier({
|
|
112
|
-
enabled: true,
|
|
113
|
-
appId: "app-id",
|
|
114
|
-
appSecret: "app-secret",
|
|
115
|
-
verificationToken: "token-1",
|
|
116
|
-
encryptKey,
|
|
117
|
-
tenantId: "tenant-a",
|
|
118
|
-
assistantId: "assistant-a",
|
|
119
|
-
mappingMode: "hybrid",
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const encrypted = encryptForTest(
|
|
123
|
-
encryptKey,
|
|
124
|
-
JSON.stringify({
|
|
125
|
-
type: "url_verification",
|
|
126
|
-
challenge: "challenge-1",
|
|
127
|
-
token: "token-1",
|
|
128
|
-
}),
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
const request = {
|
|
132
|
-
headers: {},
|
|
133
|
-
body: {
|
|
134
|
-
encrypt: encrypted,
|
|
135
|
-
},
|
|
136
|
-
} as FastifyRequest;
|
|
137
|
-
|
|
138
|
-
expect(verify(request)).toBe(true);
|
|
139
|
-
expect(parseLarkRequestBody(request.body, encryptKey)).toEqual({
|
|
140
|
-
type: "url_verification",
|
|
141
|
-
challenge: "challenge-1",
|
|
142
|
-
token: "token-1",
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
function encryptForTest(encryptKey: string, plaintext: string): string {
|
|
148
|
-
const key = crypto.createHash("sha256").update(encryptKey).digest();
|
|
149
|
-
const iv = Buffer.alloc(16, 1);
|
|
150
|
-
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
|
|
151
|
-
const ciphertext = Buffer.concat([
|
|
152
|
-
cipher.update(Buffer.from(plaintext, "utf8")),
|
|
153
|
-
cipher.final(),
|
|
154
|
-
]);
|
|
155
|
-
|
|
156
|
-
return Buffer.concat([iv, ciphertext]).toString("base64");
|
|
157
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
-
import type { ChannelInstallation, ChannelInstallationStore, LarkChannelInstallationConfig } from "@axiom-lattice/protocols";
|
|
3
|
-
import { larkChannelAdapter } from "./LarkChannelAdapter";
|
|
4
|
-
import type { MessageRouter } from "../../router/MessageRouter";
|
|
5
|
-
import { parseLarkRequestBody } from "./verification";
|
|
6
|
-
import { Logger } from "../../logger/Logger";
|
|
7
|
-
|
|
8
|
-
const logger = new Logger({ serviceName: "lattice/gateway/lark" });
|
|
9
|
-
|
|
10
|
-
export function createLarkEventHandler(deps: {
|
|
11
|
-
installationStore: ChannelInstallationStore;
|
|
12
|
-
router: MessageRouter;
|
|
13
|
-
}) {
|
|
14
|
-
return async function handleLarkEvent(
|
|
15
|
-
request: FastifyRequest,
|
|
16
|
-
reply: FastifyReply,
|
|
17
|
-
): Promise<void> {
|
|
18
|
-
const { installationId } = request.params as { installationId: string };
|
|
19
|
-
|
|
20
|
-
const installation = await deps.installationStore.getInstallationById(installationId);
|
|
21
|
-
if (!installation || installation.channel !== "lark") {
|
|
22
|
-
reply.status(404).send({ success: false, message: "Installation not found" });
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const larkInstallation = installation as ChannelInstallation<LarkChannelInstallationConfig>;
|
|
27
|
-
const body = parseLarkRequestBody(request.body, larkInstallation.config.encryptKey);
|
|
28
|
-
|
|
29
|
-
if (body.type === "url_verification" && body.challenge) {
|
|
30
|
-
reply.status(200).send({ challenge: body.challenge });
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const inboundMessage = await larkChannelAdapter.receive(request.body, larkInstallation);
|
|
35
|
-
if (!inboundMessage) {
|
|
36
|
-
reply.status(200).send();
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
deps.router.dispatch(inboundMessage).catch((error) => {
|
|
41
|
-
logger.error("Lark message dispatch error", {
|
|
42
|
-
error: error instanceof Error ? error.message : String(error),
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
reply.status(200).send();
|
|
47
|
-
};
|
|
48
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import type { ThreadStore } from "@axiom-lattice/protocols";
|
|
2
|
-
import type { ChannelIdentityMappingStore } from "@axiom-lattice/pg-stores";
|
|
3
|
-
import { randomUUID } from "crypto";
|
|
4
|
-
|
|
5
|
-
interface GetOrCreateThreadInput {
|
|
6
|
-
channel: string;
|
|
7
|
-
channelAppId: string;
|
|
8
|
-
tenantId: string;
|
|
9
|
-
assistantId: string;
|
|
10
|
-
mappingMode: "user" | "group" | "hybrid";
|
|
11
|
-
openId: string;
|
|
12
|
-
chatId: string;
|
|
13
|
-
chatType: "direct" | "group";
|
|
14
|
-
messageId: string;
|
|
15
|
-
workspaceId?: string;
|
|
16
|
-
projectId?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface ChannelThreadMappingServiceDeps {
|
|
20
|
-
mappingStore: Pick<
|
|
21
|
-
ChannelIdentityMappingStore,
|
|
22
|
-
"getMappingBySubject" | "createMapping"
|
|
23
|
-
>;
|
|
24
|
-
threadStore: Pick<ThreadStore, "createThread" | "deleteThread">;
|
|
25
|
-
uuid?: () => string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function createChannelThreadMappingService(
|
|
29
|
-
deps: ChannelThreadMappingServiceDeps,
|
|
30
|
-
) {
|
|
31
|
-
return {
|
|
32
|
-
async getOrCreateThread(input: GetOrCreateThreadInput): Promise<{ threadId: string }> {
|
|
33
|
-
const externalSubjectKey = buildExternalSubjectKey(input);
|
|
34
|
-
const existing = await deps.mappingStore.getMappingBySubject({
|
|
35
|
-
channel: input.channel,
|
|
36
|
-
channelAppId: input.channelAppId,
|
|
37
|
-
tenantId: input.tenantId,
|
|
38
|
-
assistantId: input.assistantId,
|
|
39
|
-
externalSubjectKey,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (existing) {
|
|
43
|
-
return { threadId: existing.threadId };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const threadId = (deps.uuid || randomUUID)();
|
|
47
|
-
|
|
48
|
-
await deps.threadStore.createThread(input.tenantId, input.assistantId, threadId, {
|
|
49
|
-
metadata: {
|
|
50
|
-
tenantId: input.tenantId,
|
|
51
|
-
workspaceId: input.workspaceId,
|
|
52
|
-
projectId: input.projectId,
|
|
53
|
-
channel: input.channel,
|
|
54
|
-
larkChatId: input.chatId,
|
|
55
|
-
larkOpenId: input.openId,
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
await deps.mappingStore.createMapping({
|
|
61
|
-
channel: input.channel,
|
|
62
|
-
channelAppId: input.channelAppId,
|
|
63
|
-
tenantId: input.tenantId,
|
|
64
|
-
assistantId: input.assistantId,
|
|
65
|
-
mappingMode: input.mappingMode,
|
|
66
|
-
externalSubjectType:
|
|
67
|
-
resolveSubjectType(input.mappingMode, input.chatType) === "user"
|
|
68
|
-
? "user"
|
|
69
|
-
: "chat",
|
|
70
|
-
externalSubjectKey,
|
|
71
|
-
larkOpenId: input.openId,
|
|
72
|
-
larkChatId: input.chatId,
|
|
73
|
-
larkMessageId: input.messageId,
|
|
74
|
-
threadId,
|
|
75
|
-
});
|
|
76
|
-
} catch (error) {
|
|
77
|
-
if (!isUniqueViolation(error)) {
|
|
78
|
-
throw error;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const canonical = await deps.mappingStore.getMappingBySubject({
|
|
82
|
-
channel: input.channel,
|
|
83
|
-
channelAppId: input.channelAppId,
|
|
84
|
-
tenantId: input.tenantId,
|
|
85
|
-
assistantId: input.assistantId,
|
|
86
|
-
externalSubjectKey,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
if (!canonical) {
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
await deps.threadStore.deleteThread(input.tenantId, threadId);
|
|
94
|
-
|
|
95
|
-
return { threadId: canonical.threadId };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return { threadId };
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function isUniqueViolation(error: unknown): boolean {
|
|
104
|
-
return (
|
|
105
|
-
typeof error === "object" &&
|
|
106
|
-
error !== null &&
|
|
107
|
-
"code" in error &&
|
|
108
|
-
(error as { code?: string }).code === "23505"
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function buildExternalSubjectKey(input: GetOrCreateThreadInput): string {
|
|
113
|
-
const subjectType = resolveSubjectType(input.mappingMode, input.chatType);
|
|
114
|
-
const subjectValue = subjectType === "user" ? input.openId : input.chatId;
|
|
115
|
-
|
|
116
|
-
return [
|
|
117
|
-
input.channel,
|
|
118
|
-
input.channelAppId,
|
|
119
|
-
`tenant:${input.tenantId}`,
|
|
120
|
-
`assistant:${input.assistantId}`,
|
|
121
|
-
`${subjectType}:${subjectValue}`,
|
|
122
|
-
].join(":");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function resolveSubjectType(
|
|
126
|
-
mappingMode: "user" | "group" | "hybrid",
|
|
127
|
-
chatType: "direct" | "group",
|
|
128
|
-
): "user" | "chat" {
|
|
129
|
-
if (mappingMode === "user") {
|
|
130
|
-
return "user";
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (mappingMode === "group") {
|
|
134
|
-
return "chat";
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return chatType === "direct" ? "user" : "chat";
|
|
138
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import type { ParsedLarkMessageEvent } from "./types";
|
|
2
|
-
|
|
3
|
-
interface RawLarkPayload {
|
|
4
|
-
header?: {
|
|
5
|
-
event_type?: string;
|
|
6
|
-
};
|
|
7
|
-
event?: {
|
|
8
|
-
sender?: {
|
|
9
|
-
sender_id?: {
|
|
10
|
-
open_id?: string;
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
message?: {
|
|
14
|
-
message_id?: string;
|
|
15
|
-
chat_id?: string;
|
|
16
|
-
chat_type?: string;
|
|
17
|
-
message_type?: string;
|
|
18
|
-
content?: string;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function parseLarkMessageEvent(
|
|
24
|
-
payload: unknown,
|
|
25
|
-
): ParsedLarkMessageEvent | null {
|
|
26
|
-
const raw = payload as RawLarkPayload;
|
|
27
|
-
|
|
28
|
-
if (raw.header?.event_type !== "im.message.receive_v1") {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const message = raw.event?.message;
|
|
33
|
-
const openId = raw.event?.sender?.sender_id?.open_id;
|
|
34
|
-
|
|
35
|
-
if (!message || !openId || message.message_type !== "text") {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const parsedContent = parseLarkTextContent(message.content);
|
|
40
|
-
if (!message.message_id || !message.chat_id || !parsedContent) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
messageId: message.message_id,
|
|
46
|
-
openId,
|
|
47
|
-
chatId: message.chat_id,
|
|
48
|
-
chatType: normalizeChatType(message.chat_type),
|
|
49
|
-
text: parsedContent,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function parseLarkTextContent(content: string | undefined): string | null {
|
|
54
|
-
if (!content) {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const parsed = JSON.parse(content) as { text?: string };
|
|
60
|
-
return typeof parsed.text === "string" ? parsed.text : null;
|
|
61
|
-
} catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function normalizeChatType(chatType: string | undefined): "direct" | "group" {
|
|
67
|
-
return chatType === "p2p" ? "direct" : "group";
|
|
68
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { FastifyInstance } from "fastify";
|
|
2
|
-
import { createLarkEventHandler } from "./controller";
|
|
3
|
-
import type { MessageRouter } from "../../router/MessageRouter";
|
|
4
|
-
import type { ChannelInstallationStore } from "@axiom-lattice/protocols";
|
|
5
|
-
|
|
6
|
-
export function registerLarkChannelRoutes(
|
|
7
|
-
app: FastifyInstance,
|
|
8
|
-
deps: {
|
|
9
|
-
installationStore: ChannelInstallationStore;
|
|
10
|
-
router: MessageRouter;
|
|
11
|
-
},
|
|
12
|
-
): void {
|
|
13
|
-
const handler = createLarkEventHandler({
|
|
14
|
-
installationStore: deps.installationStore,
|
|
15
|
-
router: deps.router,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
app.post(
|
|
19
|
-
"/api/channels/lark/installations/:installationId/events",
|
|
20
|
-
handler,
|
|
21
|
-
);
|
|
22
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
interface LarkSenderConfig {
|
|
2
|
-
appId: string;
|
|
3
|
-
appSecret: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
interface LarkSdkMessageClient {
|
|
7
|
-
im: {
|
|
8
|
-
v1: {
|
|
9
|
-
message: {
|
|
10
|
-
create(input: {
|
|
11
|
-
params: {
|
|
12
|
-
receive_id_type: "chat_id";
|
|
13
|
-
};
|
|
14
|
-
data: {
|
|
15
|
-
receive_id: string;
|
|
16
|
-
msg_type: "text";
|
|
17
|
-
content: string;
|
|
18
|
-
};
|
|
19
|
-
}): Promise<{ code?: number }>;
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function createLarkSender(
|
|
26
|
-
config: LarkSenderConfig,
|
|
27
|
-
client?: LarkSdkMessageClient,
|
|
28
|
-
) {
|
|
29
|
-
const resolved = client ?? (await createDefaultLarkClient(config));
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
async sendTextReply(input: { chatId: string; text: string }): Promise<void> {
|
|
33
|
-
const response = await resolved.im.v1.message.create({
|
|
34
|
-
params: {
|
|
35
|
-
receive_id_type: "chat_id",
|
|
36
|
-
},
|
|
37
|
-
data: {
|
|
38
|
-
receive_id: input.chatId,
|
|
39
|
-
msg_type: "text",
|
|
40
|
-
content: JSON.stringify({ text: input.text }),
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
if (response.code && response.code !== 0) {
|
|
45
|
-
throw new Error("Failed to send Lark reply");
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function createDefaultLarkClient(config: LarkSenderConfig): Promise<LarkSdkMessageClient> {
|
|
52
|
-
const Lark = await import("@larksuiteoapi/node-sdk");
|
|
53
|
-
|
|
54
|
-
return new Lark.Client({
|
|
55
|
-
appId: config.appId,
|
|
56
|
-
appSecret: config.appSecret,
|
|
57
|
-
}) as LarkSdkMessageClient;
|
|
58
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface ParsedLarkMessageEvent {
|
|
2
|
-
messageId: string;
|
|
3
|
-
openId: string;
|
|
4
|
-
chatId: string;
|
|
5
|
-
chatType: "direct" | "group";
|
|
6
|
-
text: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface LarkUrlVerificationPayload {
|
|
10
|
-
type?: string;
|
|
11
|
-
challenge?: string;
|
|
12
|
-
token?: string;
|
|
13
|
-
schema?: string;
|
|
14
|
-
header?: {
|
|
15
|
-
token?: string;
|
|
16
|
-
event_type?: string;
|
|
17
|
-
};
|
|
18
|
-
encrypt?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import crypto from "crypto";
|
|
2
|
-
import type { LarkUrlVerificationPayload } from "./types";
|
|
3
|
-
|
|
4
|
-
export function parseLarkRequestBody(
|
|
5
|
-
body: unknown,
|
|
6
|
-
encryptKey?: string,
|
|
7
|
-
): LarkUrlVerificationPayload {
|
|
8
|
-
const parsed = (body || {}) as LarkUrlVerificationPayload;
|
|
9
|
-
|
|
10
|
-
if (encryptKey && typeof parsed.encrypt === "string") {
|
|
11
|
-
return decryptLarkPayload(encryptKey, parsed.encrypt);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return parsed;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function decryptLarkPayload(
|
|
18
|
-
encryptKey: string,
|
|
19
|
-
encryptedPayload: string,
|
|
20
|
-
): LarkUrlVerificationPayload {
|
|
21
|
-
const key = crypto.createHash("sha256").update(encryptKey).digest();
|
|
22
|
-
const buffer = Buffer.from(encryptedPayload, "base64");
|
|
23
|
-
const iv = buffer.subarray(0, 16);
|
|
24
|
-
const ciphertext = buffer.subarray(16);
|
|
25
|
-
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
|
|
26
|
-
const plaintext = Buffer.concat([
|
|
27
|
-
decipher.update(ciphertext),
|
|
28
|
-
decipher.final(),
|
|
29
|
-
]).toString("utf8");
|
|
30
|
-
|
|
31
|
-
return JSON.parse(plaintext) as LarkUrlVerificationPayload;
|
|
32
|
-
}
|
package/src/channels/registry.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ChannelAdapter } from "@axiom-lattice/protocols";
|
|
2
|
-
|
|
3
|
-
export class ChannelAdapterRegistry {
|
|
4
|
-
private adapters = new Map<string, ChannelAdapter>();
|
|
5
|
-
|
|
6
|
-
register(adapter: ChannelAdapter): void {
|
|
7
|
-
if (this.adapters.has(adapter.channel)) {
|
|
8
|
-
throw new Error(`Channel adapter "${adapter.channel}" already registered`);
|
|
9
|
-
}
|
|
10
|
-
this.adapters.set(adapter.channel, adapter);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
get(channel: string): ChannelAdapter | undefined {
|
|
14
|
-
return this.adapters.get(channel);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
list(): string[] {
|
|
18
|
-
return Array.from(this.adapters.keys());
|
|
19
|
-
}
|
|
20
|
-
}
|