@axiom-lattice/gateway 2.1.112 → 2.1.113
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 +11 -11
- package/CHANGELOG.md +9 -0
- package/dist/index.js +1954 -273
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1764 -83
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/controllers/export-import.ts +166 -0
- package/src/controllers/run.ts +30 -4
- package/src/export_registrations/a2a-api-key.registration.ts +89 -0
- package/src/export_registrations/agent.registration.ts +80 -0
- package/src/export_registrations/binding.registration.ts +106 -0
- package/src/export_registrations/channel-installation.registration.ts +88 -0
- package/src/export_registrations/collection.registration.ts +85 -0
- package/src/export_registrations/connection.registration.ts +97 -0
- package/src/export_registrations/database-config.registration.ts +94 -0
- package/src/export_registrations/eval.registration.ts +334 -0
- package/src/export_registrations/index.ts +31 -0
- package/src/export_registrations/mcp-config.registration.ts +97 -0
- package/src/export_registrations/menu.registration.ts +91 -0
- package/src/export_registrations/metrics-config.registration.ts +94 -0
- package/src/export_registrations/skill.registration.ts +88 -0
- package/src/export_registrations/task.registration.ts +99 -0
- package/src/index.ts +4 -0
- package/src/routes/index.ts +34 -0
- package/src/services/ExportImportService.ts +296 -0
- package/src/services/__tests__/ExportImportService.test.ts +130 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/gateway",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.113",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"redis": "^5.0.1",
|
|
42
42
|
"uuid": "^9.0.1",
|
|
43
43
|
"zod": "3.25.76",
|
|
44
|
-
"@axiom-lattice/core": "2.1.
|
|
45
|
-
"@axiom-lattice/pg-stores": "1.0.
|
|
44
|
+
"@axiom-lattice/core": "2.1.100",
|
|
45
|
+
"@axiom-lattice/pg-stores": "1.0.91",
|
|
46
46
|
"@axiom-lattice/protocols": "2.1.51",
|
|
47
47
|
"@axiom-lattice/queue-redis": "1.0.50"
|
|
48
48
|
},
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
2
|
+
import { ExportImportService } from '../services/ExportImportService';
|
|
3
|
+
import { ExportableEntityRegistry } from '@axiom-lattice/core';
|
|
4
|
+
|
|
5
|
+
const service = new ExportImportService();
|
|
6
|
+
|
|
7
|
+
function getTenantId(request: FastifyRequest): string {
|
|
8
|
+
return (request as any).user?.tenantId || (request.headers['x-tenant-id'] as string) || 'default';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** GET /api/tenants/exportable-types */
|
|
12
|
+
export async function getExportableTypes(
|
|
13
|
+
_request: FastifyRequest,
|
|
14
|
+
reply: FastifyReply,
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const types = service.listExportableTypes();
|
|
17
|
+
return reply.send({ success: true, data: types });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** POST /api/tenants/:tenantId/export */
|
|
21
|
+
export async function exportConfig(
|
|
22
|
+
request: FastifyRequest<{ Params: { tenantId: string }; Body: { entityTypes: string[]; entityIds?: Record<string, string[]> } }>,
|
|
23
|
+
reply: FastifyReply,
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
const tenantId = getTenantId(request) || request.params.tenantId;
|
|
26
|
+
const { entityTypes, entityIds } = request.body;
|
|
27
|
+
|
|
28
|
+
if (!Array.isArray(entityTypes) || entityTypes.length === 0) {
|
|
29
|
+
return reply.status(400).send({ success: false, message: 'entityTypes must be a non-empty array' });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const preview = await service.exportPreview(tenantId, entityTypes);
|
|
33
|
+
|
|
34
|
+
if (preview.missingDependencies.length > 0 || preview.cascadeAdditions.length > 0) {
|
|
35
|
+
return reply.send({
|
|
36
|
+
success: true,
|
|
37
|
+
data: {
|
|
38
|
+
needsConfirmation: true,
|
|
39
|
+
missingDependencies: preview.missingDependencies,
|
|
40
|
+
cascadeAdditions: preview.cascadeAdditions,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { jobId, bundle } = await service.export(tenantId, entityTypes, entityIds);
|
|
46
|
+
return reply.send({
|
|
47
|
+
success: true,
|
|
48
|
+
data: {
|
|
49
|
+
jobId,
|
|
50
|
+
summary: Object.fromEntries(
|
|
51
|
+
Object.entries(bundle.entities).map(([type, entities]) => [type, entities.length]),
|
|
52
|
+
),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** POST /api/tenants/:tenantId/export/confirm */
|
|
58
|
+
export async function exportConfigConfirm(
|
|
59
|
+
request: FastifyRequest<{ Params: { tenantId: string }; Body: { entityTypes: string[]; entityIds?: Record<string, string[]> } }>,
|
|
60
|
+
reply: FastifyReply,
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
const tenantId = getTenantId(request) || request.params.tenantId;
|
|
63
|
+
const { entityTypes, entityIds } = request.body;
|
|
64
|
+
|
|
65
|
+
const { jobId, bundle } = await service.export(tenantId, entityTypes, entityIds);
|
|
66
|
+
return reply.send({
|
|
67
|
+
success: true,
|
|
68
|
+
data: {
|
|
69
|
+
jobId,
|
|
70
|
+
summary: Object.fromEntries(
|
|
71
|
+
Object.entries(bundle.entities).map(([type, entities]) => [type, entities.length]),
|
|
72
|
+
),
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** POST /api/tenants/:tenantId/export/entities */
|
|
78
|
+
export async function previewEntities(
|
|
79
|
+
request: FastifyRequest<{ Params: { tenantId: string }; Body: { entityTypes: string[] } }>,
|
|
80
|
+
reply: FastifyReply,
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
const tenantId = getTenantId(request) || request.params.tenantId;
|
|
83
|
+
const { entityTypes } = request.body;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const registry = ExportableEntityRegistry.getInstance();
|
|
87
|
+
const result: Record<string, Array<{ id: string; name: string; description?: string }>> = {};
|
|
88
|
+
|
|
89
|
+
for (const type of entityTypes) {
|
|
90
|
+
const def = registry.get(type);
|
|
91
|
+
const entities = await def.listForExport(tenantId);
|
|
92
|
+
result[type] = entities.map((e) => ({
|
|
93
|
+
id: e.data.id as string,
|
|
94
|
+
name: (e.data.name as string) || (e.data.id as string),
|
|
95
|
+
description: e.data.description as string | undefined,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return reply.send({ success: true, data: result });
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return reply.status(400).send({ success: false, message: (err as Error).message });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** GET /api/tenants/:tenantId/export/:jobId/download */
|
|
106
|
+
export async function downloadExport(
|
|
107
|
+
request: FastifyRequest<{ Params: { tenantId: string; jobId: string } }>,
|
|
108
|
+
reply: FastifyReply,
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
const tenantId = getTenantId(request) || request.params.tenantId;
|
|
111
|
+
const bundle = service.getExportJob(request.params.jobId, tenantId);
|
|
112
|
+
|
|
113
|
+
if (!bundle) {
|
|
114
|
+
return reply.status(404).send({ success: false, message: 'Export job not found or expired' });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return reply
|
|
118
|
+
.header('Content-Type', 'application/json')
|
|
119
|
+
.header('Content-Disposition', `attachment; filename="tenant-export-${bundle.sourceTenantId}.json"`)
|
|
120
|
+
.send({ success: true, data: bundle });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** POST /api/tenants/:tenantId/import/preview */
|
|
124
|
+
export async function importPreview(
|
|
125
|
+
request: FastifyRequest<{ Params: { tenantId: string }; Body: { bundle: any } }>,
|
|
126
|
+
reply: FastifyReply,
|
|
127
|
+
): Promise<void> {
|
|
128
|
+
const tenantId = getTenantId(request) || request.params.tenantId;
|
|
129
|
+
const { bundle } = request.body;
|
|
130
|
+
|
|
131
|
+
if (!bundle) {
|
|
132
|
+
return reply.status(400).send({ success: false, message: 'No bundle provided in request body' });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const preview = await service.previewImport(tenantId, bundle);
|
|
137
|
+
return reply.send({ success: true, data: preview });
|
|
138
|
+
} catch (err) {
|
|
139
|
+
return reply.status(400).send({
|
|
140
|
+
success: false,
|
|
141
|
+
message: `Invalid export file: ${(err as Error).message}`,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** POST /api/tenants/:tenantId/import/apply */
|
|
147
|
+
export async function importApply(
|
|
148
|
+
request: FastifyRequest<{
|
|
149
|
+
Params: { tenantId: string };
|
|
150
|
+
Body: { bundle: any; resolutions: Record<string, any> };
|
|
151
|
+
}>,
|
|
152
|
+
reply: FastifyReply,
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
const tenantId = getTenantId(request) || request.params.tenantId;
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const { bundle, resolutions } = request.body;
|
|
158
|
+
const result = await service.applyImport(tenantId, bundle, resolutions);
|
|
159
|
+
return reply.send({ success: true, data: result });
|
|
160
|
+
} catch (err) {
|
|
161
|
+
return reply.status(400).send({
|
|
162
|
+
success: false,
|
|
163
|
+
message: `Import failed: ${(err as Error).message}`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
package/src/controllers/run.ts
CHANGED
|
@@ -73,6 +73,27 @@ export const createRun = async (
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
// ADR-026: background mode — enqueue only, returns 202.
|
|
77
|
+
// Chunk delivery is handled by the thread-level resume_stream SSE.
|
|
78
|
+
if (background) {
|
|
79
|
+
const messageInput = message_id
|
|
80
|
+
? { ...input, id: message_id }
|
|
81
|
+
: input;
|
|
82
|
+
|
|
83
|
+
const result = await agent.addMessage({
|
|
84
|
+
input: messageInput,
|
|
85
|
+
command,
|
|
86
|
+
custom_run_config: mergedConfig,
|
|
87
|
+
}, mode as QueueMode);
|
|
88
|
+
|
|
89
|
+
reply.status(202).send({
|
|
90
|
+
success: true,
|
|
91
|
+
messageId: result.messageId,
|
|
92
|
+
queued: true,
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
76
97
|
// Handle streaming requests
|
|
77
98
|
if (streaming) {
|
|
78
99
|
// Setup SSE
|
|
@@ -198,12 +219,17 @@ export const resumeStream = async (
|
|
|
198
219
|
project_id,
|
|
199
220
|
});
|
|
200
221
|
|
|
201
|
-
//
|
|
202
|
-
|
|
222
|
+
// ADR-026: Thread-level SSE, lives until client disconnect.
|
|
223
|
+
// Empty stopTypes means the iterator never stops on its own —
|
|
224
|
+
// MESSAGE_COMPLETED, THREAD_IDLE etc. pass through as data.
|
|
225
|
+
// Client disconnect (close event) or client abort (reply.raw.destroyed)
|
|
226
|
+
// terminate the loop.
|
|
227
|
+
const stream = agent.chunkStream(message_id, []);
|
|
228
|
+
let closed = false;
|
|
229
|
+
request.raw.on('close', () => { closed = true; });
|
|
203
230
|
|
|
204
|
-
// Stream the chunks to the client
|
|
205
231
|
for await (const chunk of stream) {
|
|
206
|
-
|
|
232
|
+
if (closed || reply.raw.destroyed) break;
|
|
207
233
|
reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
208
234
|
}
|
|
209
235
|
} catch (error: any) {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ExportableEntityDefinition, ExportableEntity, ImportPreviewResult } from '@axiom-lattice/core';
|
|
2
|
+
import { getStoreLattice } from '@axiom-lattice/core';
|
|
3
|
+
import type { A2AApiKeyStore } from '@axiom-lattice/protocols';
|
|
4
|
+
|
|
5
|
+
function getStore(): A2AApiKeyStore {
|
|
6
|
+
return getStoreLattice('default', 'a2aApiKey').store as unknown as A2AApiKeyStore;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const a2aApiKeyRegistration: ExportableEntityDefinition = {
|
|
10
|
+
entityType: 'a2a_api_key',
|
|
11
|
+
label: 'A2A API Key(API密钥)',
|
|
12
|
+
category: 'core',
|
|
13
|
+
dependsOn: [],
|
|
14
|
+
cascadeParents: [],
|
|
15
|
+
|
|
16
|
+
async listForExport(tenantId: string): Promise<ExportableEntity[]> {
|
|
17
|
+
const store = getStore();
|
|
18
|
+
const keys = await store.list({ tenantId, limit: 10000, offset: 0 });
|
|
19
|
+
return keys.map((k) => ({
|
|
20
|
+
_exportId: '',
|
|
21
|
+
data: {
|
|
22
|
+
id: k.id,
|
|
23
|
+
tenantId: k.tenantId,
|
|
24
|
+
projectId: k.projectId,
|
|
25
|
+
workspaceId: k.workspaceId,
|
|
26
|
+
label: k.label,
|
|
27
|
+
enabled: k.enabled,
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async previewImport(
|
|
33
|
+
tenantId: string,
|
|
34
|
+
entities: ExportableEntity[],
|
|
35
|
+
): Promise<ImportPreviewResult> {
|
|
36
|
+
const store = getStore();
|
|
37
|
+
const existing = await store.list({ tenantId, limit: 10000, offset: 0 });
|
|
38
|
+
const existingIds = new Set(existing.map((k) => k.id));
|
|
39
|
+
|
|
40
|
+
const conflicts: ImportPreviewResult['conflicts'] = [];
|
|
41
|
+
const insertions: ImportPreviewResult['insertions'] = [];
|
|
42
|
+
|
|
43
|
+
for (const entity of entities) {
|
|
44
|
+
const d = entity.data as Record<string, unknown>;
|
|
45
|
+
const id = d.id as string;
|
|
46
|
+
|
|
47
|
+
if (existingIds.has(id)) {
|
|
48
|
+
conflicts.push({
|
|
49
|
+
_exportId: entity._exportId,
|
|
50
|
+
entityType: 'a2a_api_key',
|
|
51
|
+
conflictType: 'id_exists',
|
|
52
|
+
existingId: id,
|
|
53
|
+
existingName: (d.label as string) || id,
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
insertions.push({
|
|
57
|
+
_exportId: entity._exportId,
|
|
58
|
+
entityType: 'a2a_api_key',
|
|
59
|
+
name: (d.label as string) || id,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { conflicts, insertions, errors: [] };
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
async applyImport(tenantId, entity, resolution) {
|
|
68
|
+
if (resolution.action === 'skip') return {};
|
|
69
|
+
|
|
70
|
+
const store = getStore();
|
|
71
|
+
const d = entity.data as Record<string, unknown>;
|
|
72
|
+
|
|
73
|
+
if (resolution.action === 'overwrite') {
|
|
74
|
+
try {
|
|
75
|
+
await store.delete(d.id as string);
|
|
76
|
+
} catch { /* may not exist */ }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Always generate a new key_value — key_value is globally unique
|
|
80
|
+
await store.create({
|
|
81
|
+
tenantId,
|
|
82
|
+
projectId: d.projectId as string | undefined,
|
|
83
|
+
workspaceId: d.workspaceId as string | undefined,
|
|
84
|
+
label: d.label as string | undefined,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return { newId: undefined };
|
|
88
|
+
},
|
|
89
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ExportableEntityDefinition, ExportableEntity, ImportPreviewResult } from '@axiom-lattice/core';
|
|
2
|
+
import { getStoreLattice } from '@axiom-lattice/core';
|
|
3
|
+
import type { AssistantStore } from '@axiom-lattice/protocols';
|
|
4
|
+
|
|
5
|
+
function getStore(): AssistantStore {
|
|
6
|
+
return getStoreLattice('default', 'assistant').store as unknown as AssistantStore;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const agentRegistration: ExportableEntityDefinition = {
|
|
10
|
+
entityType: 'agent',
|
|
11
|
+
label: 'Agent(智能体)',
|
|
12
|
+
category: 'core',
|
|
13
|
+
dependsOn: ['skill'],
|
|
14
|
+
cascadeParents: [],
|
|
15
|
+
|
|
16
|
+
async listForExport(tenantId: string): Promise<ExportableEntity[]> {
|
|
17
|
+
const store = getStore();
|
|
18
|
+
const agents = await store.getAllAssistants(tenantId);
|
|
19
|
+
return agents.map((a) => ({
|
|
20
|
+
_exportId: '',
|
|
21
|
+
data: {
|
|
22
|
+
id: a.id,
|
|
23
|
+
name: a.name,
|
|
24
|
+
description: a.description,
|
|
25
|
+
graphDefinition: a.graphDefinition,
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
async previewImport(
|
|
31
|
+
tenantId: string,
|
|
32
|
+
entities: ExportableEntity[],
|
|
33
|
+
): Promise<ImportPreviewResult> {
|
|
34
|
+
const store = getStore();
|
|
35
|
+
const existing = await store.getAllAssistants(tenantId);
|
|
36
|
+
const existingIds = new Set(existing.map((a) => a.id));
|
|
37
|
+
|
|
38
|
+
const conflicts: ImportPreviewResult['conflicts'] = [];
|
|
39
|
+
const insertions: ImportPreviewResult['insertions'] = [];
|
|
40
|
+
|
|
41
|
+
for (const entity of entities) {
|
|
42
|
+
const id = entity.data.id as string;
|
|
43
|
+
if (existingIds.has(id)) {
|
|
44
|
+
conflicts.push({
|
|
45
|
+
_exportId: entity._exportId,
|
|
46
|
+
entityType: 'agent',
|
|
47
|
+
conflictType: 'id_exists',
|
|
48
|
+
existingId: id,
|
|
49
|
+
existingName: (entity.data.name as string) || id,
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
insertions.push({
|
|
53
|
+
_exportId: entity._exportId,
|
|
54
|
+
entityType: 'agent',
|
|
55
|
+
name: (entity.data.name as string) || id,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { conflicts, insertions, errors: [] };
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
async applyImport(tenantId, entity, resolution) {
|
|
64
|
+
const store = getStore();
|
|
65
|
+
const data = entity.data as Record<string, unknown>;
|
|
66
|
+
const id = resolution.action === 'rename' && resolution.newId ? resolution.newId : data.id as string;
|
|
67
|
+
|
|
68
|
+
if (resolution.action === 'overwrite') {
|
|
69
|
+
try { await store.deleteAssistant(tenantId, id); } catch { /* may not exist */ }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await store.createAssistant(tenantId, id, {
|
|
73
|
+
name: data.name as string,
|
|
74
|
+
description: (data.description as string) || '',
|
|
75
|
+
graphDefinition: data.graphDefinition as Record<string, unknown>,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return { newId: id };
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ExportableEntityDefinition, ExportableEntity, ImportPreviewResult } from '@axiom-lattice/core';
|
|
2
|
+
import { getStoreLattice } from '@axiom-lattice/core';
|
|
3
|
+
import type { BindingRegistry } from '@axiom-lattice/protocols';
|
|
4
|
+
|
|
5
|
+
function getStore(): BindingRegistry {
|
|
6
|
+
return getStoreLattice('default', 'channelBinding').store as unknown as BindingRegistry;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const bindingRegistration: ExportableEntityDefinition = {
|
|
10
|
+
entityType: 'binding',
|
|
11
|
+
label: 'Binding(渠道绑定)',
|
|
12
|
+
category: 'core',
|
|
13
|
+
dependsOn: ['agent', 'channel_installation'],
|
|
14
|
+
cascadeParents: [],
|
|
15
|
+
referenceFields: { agentId: 'agent', channelInstallationId: 'channel_installation' },
|
|
16
|
+
|
|
17
|
+
async listForExport(tenantId: string): Promise<ExportableEntity[]> {
|
|
18
|
+
const store = getStore();
|
|
19
|
+
const bindings = await store.list({ tenantId, limit: 10000, offset: 0 });
|
|
20
|
+
return bindings.map((b) => ({
|
|
21
|
+
_exportId: '',
|
|
22
|
+
data: {
|
|
23
|
+
channel: b.channel,
|
|
24
|
+
channelInstallationId: b.channelInstallationId,
|
|
25
|
+
senderId: b.senderId,
|
|
26
|
+
agentId: b.agentId,
|
|
27
|
+
threadId: b.threadId,
|
|
28
|
+
workspaceId: b.workspaceId,
|
|
29
|
+
projectId: b.projectId,
|
|
30
|
+
threadMode: b.threadMode,
|
|
31
|
+
senderDisplayName: b.senderDisplayName,
|
|
32
|
+
senderMetadata: b.senderMetadata,
|
|
33
|
+
enabled: b.enabled,
|
|
34
|
+
},
|
|
35
|
+
}));
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
async previewImport(
|
|
39
|
+
tenantId: string,
|
|
40
|
+
entities: ExportableEntity[],
|
|
41
|
+
): Promise<ImportPreviewResult> {
|
|
42
|
+
const store = getStore();
|
|
43
|
+
const existing = await store.list({ tenantId, limit: 10000, offset: 0 });
|
|
44
|
+
|
|
45
|
+
const existingKeys = new Set(
|
|
46
|
+
existing.map((b) => `${b.channel}:${b.channelInstallationId}:${b.senderId}`),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const conflicts: ImportPreviewResult['conflicts'] = [];
|
|
50
|
+
const insertions: ImportPreviewResult['insertions'] = [];
|
|
51
|
+
|
|
52
|
+
for (const entity of entities) {
|
|
53
|
+
const d = entity.data as Record<string, unknown>;
|
|
54
|
+
const key = `${d.channel}:${d.channelInstallationId}:${d.senderId}`;
|
|
55
|
+
if (existingKeys.has(key)) {
|
|
56
|
+
conflicts.push({
|
|
57
|
+
_exportId: entity._exportId,
|
|
58
|
+
entityType: 'binding',
|
|
59
|
+
conflictType: 'unique_constraint',
|
|
60
|
+
existingName: `${d.channel}:${d.senderId}`,
|
|
61
|
+
field: 'channel+channelInstallationId+senderId',
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
insertions.push({
|
|
65
|
+
_exportId: entity._exportId,
|
|
66
|
+
entityType: 'binding',
|
|
67
|
+
name: `${d.channel}:${d.senderId}`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { conflicts, insertions, errors: [] };
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
async applyImport(tenantId, entity, resolution) {
|
|
76
|
+
if (resolution.action === 'skip') return {};
|
|
77
|
+
|
|
78
|
+
const store = getStore();
|
|
79
|
+
const d = entity.data as Record<string, unknown>;
|
|
80
|
+
|
|
81
|
+
if (resolution.action === 'overwrite') {
|
|
82
|
+
const existing = await store.list({ tenantId, limit: 10000, offset: 0 });
|
|
83
|
+
const match = existing.find(
|
|
84
|
+
(b) => b.channel === d.channel && b.channelInstallationId === d.channelInstallationId && b.senderId === d.senderId,
|
|
85
|
+
);
|
|
86
|
+
if (match) {
|
|
87
|
+
await store.delete(match.id);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await store.create({
|
|
92
|
+
channel: d.channel as string,
|
|
93
|
+
channelInstallationId: d.channelInstallationId as string,
|
|
94
|
+
tenantId,
|
|
95
|
+
senderId: d.senderId as string,
|
|
96
|
+
agentId: d.agentId as string,
|
|
97
|
+
threadMode: (d.threadMode as 'fixed' | 'per_conversation') || 'fixed',
|
|
98
|
+
senderDisplayName: d.senderDisplayName as string | undefined,
|
|
99
|
+
senderMetadata: d.senderMetadata as Record<string, unknown> | undefined,
|
|
100
|
+
workspaceId: d.workspaceId as string | undefined,
|
|
101
|
+
projectId: d.projectId as string | undefined,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return { newId: undefined };
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ExportableEntityDefinition, ExportableEntity, ImportPreviewResult } from '@axiom-lattice/core';
|
|
2
|
+
import { getStoreLattice } from '@axiom-lattice/core';
|
|
3
|
+
import type { ChannelInstallationStore } from '@axiom-lattice/protocols';
|
|
4
|
+
|
|
5
|
+
function getStore(): ChannelInstallationStore {
|
|
6
|
+
return getStoreLattice('default', 'channelInstallation').store as unknown as ChannelInstallationStore;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const channelInstallationRegistration: ExportableEntityDefinition = {
|
|
10
|
+
entityType: 'channel_installation',
|
|
11
|
+
label: 'Channel Installation(渠道安装)',
|
|
12
|
+
category: 'core',
|
|
13
|
+
dependsOn: ['agent'],
|
|
14
|
+
cascadeParents: [],
|
|
15
|
+
referenceFields: { fallbackAgentId: 'agent' },
|
|
16
|
+
|
|
17
|
+
async listForExport(tenantId: string): Promise<ExportableEntity[]> {
|
|
18
|
+
const store = getStore();
|
|
19
|
+
const installations = await store.getInstallationsByTenant(tenantId);
|
|
20
|
+
return installations.map((i) => ({
|
|
21
|
+
_exportId: '',
|
|
22
|
+
data: {
|
|
23
|
+
id: i.id,
|
|
24
|
+
channel: i.channel,
|
|
25
|
+
name: i.name,
|
|
26
|
+
config: i.config,
|
|
27
|
+
enabled: i.enabled,
|
|
28
|
+
fallbackAgentId: i.fallbackAgentId,
|
|
29
|
+
rejectWhenNoBinding: i.rejectWhenNoBinding,
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async previewImport(
|
|
35
|
+
tenantId: string,
|
|
36
|
+
entities: ExportableEntity[],
|
|
37
|
+
): Promise<ImportPreviewResult> {
|
|
38
|
+
const store = getStore();
|
|
39
|
+
const existing = await store.getInstallationsByTenant(tenantId);
|
|
40
|
+
const existingIds = new Set(existing.map((i) => i.id));
|
|
41
|
+
|
|
42
|
+
const conflicts: ImportPreviewResult['conflicts'] = [];
|
|
43
|
+
const insertions: ImportPreviewResult['insertions'] = [];
|
|
44
|
+
|
|
45
|
+
for (const entity of entities) {
|
|
46
|
+
const d = entity.data as Record<string, unknown>;
|
|
47
|
+
const id = d.id as string;
|
|
48
|
+
if (existingIds.has(id)) {
|
|
49
|
+
conflicts.push({
|
|
50
|
+
_exportId: entity._exportId,
|
|
51
|
+
entityType: 'channel_installation',
|
|
52
|
+
conflictType: 'id_exists',
|
|
53
|
+
existingId: id,
|
|
54
|
+
existingName: (d.name as string) || id,
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
insertions.push({
|
|
58
|
+
_exportId: entity._exportId,
|
|
59
|
+
entityType: 'channel_installation',
|
|
60
|
+
name: (d.name as string) || id,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { conflicts, insertions, errors: [] };
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async applyImport(tenantId, entity, resolution) {
|
|
69
|
+
const store = getStore();
|
|
70
|
+
const data = entity.data as Record<string, unknown>;
|
|
71
|
+
const id = resolution.action === 'rename' && resolution.newId ? resolution.newId : data.id as string;
|
|
72
|
+
|
|
73
|
+
if (resolution.action === 'overwrite') {
|
|
74
|
+
try { await store.deleteInstallation(tenantId, id); } catch { /* may not exist */ }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await store.createInstallation(tenantId, id, {
|
|
78
|
+
channel: data.channel as 'lark' | 'email' | 'slack' | 'wechat',
|
|
79
|
+
name: data.name as string | undefined,
|
|
80
|
+
config: data.config as any,
|
|
81
|
+
enabled: data.enabled as boolean | undefined,
|
|
82
|
+
fallbackAgentId: data.fallbackAgentId as string | undefined,
|
|
83
|
+
rejectWhenNoBinding: data.rejectWhenNoBinding as boolean | undefined,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return { newId: id };
|
|
87
|
+
},
|
|
88
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ExportableEntityDefinition, ExportableEntity, ImportPreviewResult } from '@axiom-lattice/core';
|
|
2
|
+
import { getStoreLattice } from '@axiom-lattice/core';
|
|
3
|
+
import type { CollectionStore } from '@axiom-lattice/protocols';
|
|
4
|
+
|
|
5
|
+
function getStore(): CollectionStore {
|
|
6
|
+
return getStoreLattice('default', 'collection').store as unknown as CollectionStore;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const collectionRegistration: ExportableEntityDefinition = {
|
|
10
|
+
entityType: 'collection',
|
|
11
|
+
label: 'Collection(知识库)',
|
|
12
|
+
category: 'core',
|
|
13
|
+
dependsOn: [],
|
|
14
|
+
cascadeParents: [],
|
|
15
|
+
|
|
16
|
+
async listForExport(tenantId: string): Promise<ExportableEntity[]> {
|
|
17
|
+
const store = getStore();
|
|
18
|
+
const collections = await store.getAllCollections(tenantId);
|
|
19
|
+
return collections.map((c) => ({
|
|
20
|
+
_exportId: '',
|
|
21
|
+
data: {
|
|
22
|
+
id: c.id,
|
|
23
|
+
name: c.name,
|
|
24
|
+
label: c.label,
|
|
25
|
+
embeddingKey: c.embeddingKey,
|
|
26
|
+
schema: c.schema,
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
async previewImport(
|
|
32
|
+
tenantId: string,
|
|
33
|
+
entities: ExportableEntity[],
|
|
34
|
+
): Promise<ImportPreviewResult> {
|
|
35
|
+
const store = getStore();
|
|
36
|
+
const existing = await store.getAllCollections(tenantId);
|
|
37
|
+
const existingNames = new Set(existing.map((c) => c.name));
|
|
38
|
+
|
|
39
|
+
const conflicts: ImportPreviewResult['conflicts'] = [];
|
|
40
|
+
const insertions: ImportPreviewResult['insertions'] = [];
|
|
41
|
+
|
|
42
|
+
for (const entity of entities) {
|
|
43
|
+
const d = entity.data as Record<string, unknown>;
|
|
44
|
+
const name = d.name as string;
|
|
45
|
+
if (existingNames.has(name)) {
|
|
46
|
+
conflicts.push({
|
|
47
|
+
_exportId: entity._exportId,
|
|
48
|
+
entityType: 'collection',
|
|
49
|
+
conflictType: 'unique_constraint',
|
|
50
|
+
existingName: name,
|
|
51
|
+
field: 'name',
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
insertions.push({
|
|
55
|
+
_exportId: entity._exportId,
|
|
56
|
+
entityType: 'collection',
|
|
57
|
+
name: name,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { conflicts, insertions, errors: [] };
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async applyImport(tenantId, entity, resolution) {
|
|
66
|
+
if (resolution.action === 'skip') return {};
|
|
67
|
+
|
|
68
|
+
const store = getStore();
|
|
69
|
+
const d = entity.data as Record<string, unknown>;
|
|
70
|
+
const name = d.name as string;
|
|
71
|
+
|
|
72
|
+
if (resolution.action === 'overwrite') {
|
|
73
|
+
try { await store.deleteCollection(tenantId, name); } catch { /* may not exist */ }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await store.createCollection(tenantId, {
|
|
77
|
+
name,
|
|
78
|
+
label: d.label as string,
|
|
79
|
+
embeddingKey: d.embeddingKey as string,
|
|
80
|
+
schema: d.schema as any,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return { newId: undefined };
|
|
84
|
+
},
|
|
85
|
+
};
|