@axiom-lattice/gateway 2.1.92 → 2.1.93

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/src/index.ts CHANGED
@@ -11,7 +11,7 @@ import type { ChannelInstallationStore, BindingRegistry } from "@axiom-lattice/p
11
11
  import { MessageRouter } from "./router/MessageRouter";
12
12
  import { ChannelAdapterRegistry } from "./channels/registry";
13
13
  import { createDeduplicationMiddleware, createRateLimitMiddleware, createAuditLoggerMiddleware } from "./router/middlewares";
14
- import { setBindingRegistry } from "@axiom-lattice/core";
14
+ import { setBindingRegistry, setMenuRegistry } from "@axiom-lattice/core";
15
15
  import { larkChannelAdapter } from "./channels/lark/LarkChannelAdapter";
16
16
  import { extractUserFromAuthHeader } from "./controllers/auth";
17
17
  import { configureSwagger } from "./swagger";
@@ -277,17 +277,22 @@ const start = async (config?: LatticeGatewayConfig) => {
277
277
  // Access via: request.server.loggerLattice or app.loggerLattice
278
278
  app.decorate("loggerLattice", loggerLattice);
279
279
 
280
- // Channel infrastructure: use stores from StoreLatticeManager (defaults: InMemory).
281
- // Projects can override with PG stores via registerStoreLattice before start().
280
+ // Channel infrastructure: use stores from StoreLatticeManager.
281
+ // Applications (e.g. deep_research) register stores via configureStores()
282
+ // before calling startAsHttpEndpoint(). If no stores are registered,
283
+ // channel features are gracefully skipped.
282
284
  let channelDeps: { router: MessageRouter; installationStore: ChannelInstallationStore } | undefined;
285
+ const adapterRegistry = new ChannelAdapterRegistry();
286
+ adapterRegistry.register(larkChannelAdapter);
283
287
  try {
284
- const { getStoreLattice } = await import("@axiom-lattice/core");
285
- const bindingStore = getStoreLattice("default", "channelBinding").store as BindingRegistry;
286
- const installationStore = getStoreLattice("default", "channelInstallation").store as ChannelInstallationStore;
287
- setBindingRegistry(bindingStore);
288
+ const { getStoreLattice: getStore } = await import("@axiom-lattice/core");
289
+ let bindingStore: BindingRegistry;
290
+ let installationStore: ChannelInstallationStore;
291
+
292
+ bindingStore = getStore("default", "channelBinding").store as BindingRegistry;
293
+ installationStore = getStore("default", "channelInstallation").store as ChannelInstallationStore;
288
294
 
289
- const adapterRegistry = new ChannelAdapterRegistry();
290
- adapterRegistry.register(larkChannelAdapter);
295
+ setBindingRegistry(bindingStore);
291
296
 
292
297
  const router = new MessageRouter({
293
298
  middlewares: [
@@ -305,7 +310,7 @@ const start = async (config?: LatticeGatewayConfig) => {
305
310
 
306
311
  // Initialize A2A API key store
307
312
  try {
308
- const a2aKeyStore = getStoreLattice("default", "a2aApiKey").store as import("@axiom-lattice/protocols").A2AApiKeyStore;
313
+ const a2aKeyStore = getStore("default", "a2aApiKey").store as import("@axiom-lattice/protocols").A2AApiKeyStore;
309
314
  const a2a = await import("./routes/a2a");
310
315
  a2a.setA2AKeyStore(a2aKeyStore);
311
316
  await a2a.refreshStoreKeyMap();
@@ -313,8 +318,19 @@ const start = async (config?: LatticeGatewayConfig) => {
313
318
  } catch {
314
319
  // A2A key store optional — env-based keys still work
315
320
  }
321
+ } catch (err) {
322
+ logger.warn("Channel infrastructure unavailable", {
323
+ error: err instanceof Error ? err.message : String(err),
324
+ });
325
+ }
326
+
327
+ // Initialize MenuRegistry for custom menu items
328
+ try {
329
+ const menuStore = getStoreLattice("default", "menu").store;
330
+ setMenuRegistry(menuStore);
331
+ logger.info("Menu registry initialized");
316
332
  } catch {
317
- // Stores not registeredchannel routes will be skipped gracefully
333
+ // Menu store optionalcustom menus won't work without it
318
334
  }
319
335
 
320
336
  // Register all routes (channel routes only active if channelDeps is set)
@@ -326,6 +342,21 @@ const start = async (config?: LatticeGatewayConfig) => {
326
342
  logger.info("Registered sandbox manager from env configuration");
327
343
  }
328
344
 
345
+ // Connect all enabled channel installations
346
+ if (channelDeps && process.env.CHANNELS_ENABLED !== "false") {
347
+ const { connectAllChannels } = await import("@axiom-lattice/core");
348
+ try {
349
+ await connectAllChannels(
350
+ (channel) => adapterRegistry.get(channel),
351
+ { deps: { router: channelDeps.router } },
352
+ );
353
+ } catch (err) {
354
+ logger.error("Failed to start channel connections", {
355
+ error: err instanceof Error ? err.message : String(err),
356
+ });
357
+ }
358
+ }
359
+
329
360
  const target_port = config?.port || Number(process.env.PORT) || 4001;
330
361
 
331
362
  await app.listen({ port: target_port, host: "0.0.0.0" });
@@ -348,15 +379,14 @@ const start = async (config?: LatticeGatewayConfig) => {
348
379
  }
349
380
  }
350
381
 
351
- // Restore agent instances with pending messages
352
- try {
353
- logger.info("Starting agent instance recovery...");
354
- const restoreStats = await agentInstanceManager.restore();
355
- logger.info(`Agent recovery complete: ${restoreStats.restored} threads restored, ${restoreStats.errors} errors`);
356
- } catch (error) {
357
- logger.error("Agent recovery failed", { error });
358
- // Don't exit - server can still function even if recovery fails
359
- }
382
+ // Restore agent instances with pending messages (fire-and-forget to avoid blocking startup)
383
+ agentInstanceManager.restore()
384
+ .then((stats) => {
385
+ logger.info(`Agent recovery complete: ${stats.restored} threads restored, ${stats.errors} errors`);
386
+ })
387
+ .catch((error) => {
388
+ logger.error("Agent recovery failed", { error });
389
+ });
360
390
  } catch (err) {
361
391
  logger.error("Server start failed", { error: err });
362
392
  process.exit(1);
@@ -6,6 +6,7 @@ import type {
6
6
  InboundMessage,
7
7
  DispatchResult,
8
8
  BindingRegistry,
9
+ Binding,
9
10
  ChannelInstallationStore,
10
11
  } from "@axiom-lattice/protocols";
11
12
  import { ChannelAdapterRegistry } from "../channels/registry";
@@ -112,6 +113,10 @@ export class MessageRouter {
112
113
  metadata: {},
113
114
  };
114
115
 
116
+ let binding: Binding | null = null;
117
+ let threadId: string | undefined;
118
+ let agentId: string | undefined;
119
+
115
120
  try {
116
121
  await this.runMiddlewares(ctx, async () => {
117
122
  // Resolve tenantId from installation if not provided
@@ -133,18 +138,22 @@ export class MessageRouter {
133
138
 
134
139
  console.log({ event: "dispatch:start", channel: message.channel, senderId: message.sender.id, tenantId }, "Message dispatch started");
135
140
 
136
- let binding = await this.bindingRegistry.resolve({
141
+ // 检查 adapter 是否支持自定义 thread 策略
142
+ const adapter = this.adapterRegistry.get(message.channel);
143
+ const hasAdapterThreadStrategy = !!adapter?.resolveThreadId;
144
+
145
+ binding = await this.bindingRegistry.resolve({
137
146
  channel: message.channel,
138
147
  senderId: message.sender.id,
139
148
  channelInstallationId: message.channelInstallationId,
140
149
  tenantId,
141
150
  });
142
151
 
143
- if (!binding) {
144
- const installation = await this.installationStore.getInstallationById(
145
- message.channelInstallationId,
146
- );
152
+ const installation = await this.installationStore.getInstallationById(
153
+ message.channelInstallationId,
154
+ );
147
155
 
156
+ if (!binding) {
148
157
  if (installation?.rejectWhenNoBinding) {
149
158
  console.warn({
150
159
  event: "dispatch:no_binding",
@@ -178,7 +187,8 @@ export class MessageRouter {
178
187
  createdAt: new Date(),
179
188
  updatedAt: new Date(),
180
189
  };
181
- } else {
190
+ } else if (!hasAdapterThreadStrategy) {
191
+ // 只有当 adapter 没有自定义 thread 策略且没有 fallback 时才报错
182
192
  console.error({
183
193
  event: "dispatch:no_fallback",
184
194
  channel: message.channel,
@@ -191,53 +201,110 @@ export class MessageRouter {
191
201
  }
192
202
  }
193
203
 
194
- ctx.binding = binding;
204
+ ctx.binding = binding ?? undefined;
195
205
 
196
- console.log({
197
- event: "dispatch:binding",
198
- bindingId: binding.id,
199
- agentId: binding.agentId,
200
- threadId: binding.threadId,
201
- threadMode: binding.threadMode,
202
- workspaceId: binding.workspaceId,
203
- projectId: binding.projectId,
204
- }, "Binding resolved");
205
-
206
- if (!binding.enabled) {
207
- console.warn({
208
- event: "dispatch:binding_disabled",
206
+ if (binding) {
207
+ console.log({
208
+ event: "dispatch:binding",
209
209
  bindingId: binding.id,
210
210
  agentId: binding.agentId,
211
- senderId: message.sender.id,
212
- }, "Binding is disabled, rejecting message");
211
+ threadId: binding.threadId,
212
+ threadMode: binding.threadMode,
213
+ workspaceId: binding.workspaceId,
214
+ projectId: binding.projectId,
215
+ }, "Binding resolved");
216
+
217
+ if (!binding.enabled) {
218
+ console.warn({
219
+ event: "dispatch:binding_disabled",
220
+ bindingId: binding.id,
221
+ agentId: binding.agentId,
222
+ senderId: message.sender.id,
223
+ }, "Binding is disabled, rejecting message");
224
+ throw new BindingNotFoundError(
225
+ `Binding for sender "${message.sender.id}" is disabled`,
226
+ );
227
+ }
228
+ }
229
+
230
+ // 确定 agentId
231
+ agentId = binding?.agentId ?? installation?.fallbackAgentId;
232
+ if (!agentId) {
213
233
  throw new BindingNotFoundError(
214
- `Binding for sender "${message.sender.id}" is disabled`,
234
+ `No agent configured for sender "${message.sender.id}"`,
215
235
  );
216
236
  }
217
237
 
218
- let threadId: string | undefined = ctx.binding.threadMode === "per_conversation"
219
- ? undefined // always create new thread for per_conversation
220
- : ctx.binding.threadId;
238
+ // 优先使用 adapter 的自定义 thread 策略
239
+ if (hasAdapterThreadStrategy) {
240
+ const resolvedThreadId = await adapter!.resolveThreadId!(message, binding);
241
+ threadId = resolvedThreadId;
242
+ console.log({
243
+ event: "dispatch:thread:adapter",
244
+ threadId,
245
+ channel: message.channel,
246
+ adapterChannel: adapter!.channel,
247
+ }, "Thread resolved by adapter strategy");
248
+
249
+ // 确保 thread 存在(adapter 的策略可能有重复,例如同一天)
250
+ const threadStore = getStoreLattice("default", "thread").store;
251
+ try {
252
+ await threadStore.createThread(
253
+ tenantId!,
254
+ agentId,
255
+ threadId,
256
+ {
257
+ metadata: {
258
+ channel: message.channel,
259
+ channelInstallationId: message.channelInstallationId,
260
+ senderId: message.sender.id,
261
+ bindingId: binding?.id,
262
+ ...(message.conversation ? {
263
+ conversationId: message.conversation.id,
264
+ conversationType: message.conversation.type,
265
+ } : {}),
266
+ },
267
+ },
268
+ );
269
+ console.log({
270
+ event: "dispatch:thread:adapter:created",
271
+ threadId,
272
+ }, "Thread created by adapter strategy");
273
+ } catch {
274
+ // Thread already exists from a previous message — 复用
275
+ console.log({
276
+ event: "dispatch:thread:adapter:reuse",
277
+ threadId,
278
+ }, "Thread already exists, reusing");
279
+ }
280
+ } else if (binding) {
281
+ // 默认策略:per_conversation 总是创建新线程,fixed 复用已有线程
282
+ if (binding.threadMode === "per_conversation") {
283
+ threadId = undefined; // 创建新线程
284
+ } else {
285
+ threadId = binding.threadId; // 复用 fixed 线程
286
+ }
287
+ }
221
288
 
222
289
  if (!threadId) {
223
290
  const threadStore = getStoreLattice("default", "thread").store;
224
291
  const newThreadId = randomUUID();
225
292
  console.log({
226
293
  event: "dispatch:thread:create",
227
- agentId: ctx.binding.agentId,
294
+ agentId,
228
295
  newThreadId,
229
296
  tenantId: tenantId!,
230
297
  }, "Creating new thread for binding");
231
298
  const newThread = await threadStore.createThread(
232
299
  tenantId!,
233
- ctx.binding.agentId,
300
+ agentId,
234
301
  newThreadId,
235
302
  {
236
303
  metadata: {
237
304
  channel: message.channel,
238
305
  channelInstallationId: message.channelInstallationId,
239
306
  senderId: message.sender.id,
240
- bindingId: ctx.binding.id,
307
+ bindingId: binding?.id,
241
308
  ...(message.conversation ? {
242
309
  conversationId: message.conversation.id,
243
310
  conversationType: message.conversation.type,
@@ -246,29 +313,28 @@ export class MessageRouter {
246
313
  },
247
314
  );
248
315
  threadId = newThread.id;
249
- if (ctx.binding.id !== "fallback") {
250
- await this.bindingRegistry.update(ctx.binding.id, { threadId });
251
- ctx.binding.threadId = threadId;
252
- } else {
253
- ctx.binding.threadId = threadId;
316
+ if (binding && binding.id !== "fallback") {
317
+ await this.bindingRegistry.update(binding.id, { threadId });
318
+ binding.threadId = threadId;
319
+ } else if (binding) {
320
+ binding.threadId = threadId;
254
321
  }
255
322
  }
256
323
 
257
324
  console.log({
258
325
  event: "dispatch:agent",
259
- agentId: ctx.binding.agentId,
326
+ agentId,
260
327
  threadId,
261
- threadMode: ctx.binding.threadMode,
262
328
  senderId: message.sender.id,
263
329
  contentLength: message.content.text.length,
264
330
  }, "Dispatching to agent");
265
331
 
266
332
  const agent = agentInstanceManager.getAgent({
267
333
  tenant_id: tenantId!,
268
- assistant_id: ctx.binding.agentId,
334
+ assistant_id: agentId,
269
335
  thread_id: threadId,
270
- workspace_id: ctx.binding.workspaceId || "",
271
- project_id: ctx.binding.projectId || "",
336
+ workspace_id: binding?.workspaceId || "",
337
+ project_id: binding?.projectId || "",
272
338
  });
273
339
 
274
340
  /**
@@ -383,7 +449,7 @@ export class MessageRouter {
383
449
 
384
450
  console.log({
385
451
  event: "dispatch:complete",
386
- agentId: ctx.binding.agentId,
452
+ agentId,
387
453
  threadId,
388
454
  messageId: (addResult as Record<string, unknown>)?.messageId,
389
455
  result: JSON.stringify(addResult),
@@ -393,7 +459,7 @@ export class MessageRouter {
393
459
  return {
394
460
  success: true,
395
461
  bindingId: ctx.binding?.id,
396
- threadId: ctx.binding?.threadId,
462
+ threadId,
397
463
  result: ctx.result,
398
464
  };
399
465
  } catch (error) {
@@ -14,6 +14,8 @@ import * as skillsController from "../controllers/skills";
14
14
  import * as toolsController from "../controllers/tools";
15
15
  import * as dataQueryController from "../controllers/data-query";
16
16
  import * as workflowTrackingController from "../controllers/workflow-tracking";
17
+ import * as paController from "../controllers/personal-assistant";
18
+ import * as tasksController from "../controllers/tasks";
17
19
  import {
18
20
  createRunSchema,
19
21
  getAllMemoryItemsSchema,
@@ -44,6 +46,7 @@ import { registerAuthRoutes } from "../controllers/auth";
44
46
  import { registerChannelRoutes } from "../channels/routes";
45
47
  import { registerChannelInstallationRoutes } from "./channel-installations";
46
48
  import { registerChannelBindingRoutes } from "./channel-bindings";
49
+ import { registerMenuItemRoutes } from "./menu-items";
47
50
  import type { MessageRouter } from "../router/MessageRouter";
48
51
  import type { ChannelInstallationStore } from "@axiom-lattice/protocols";
49
52
  // import {
@@ -76,6 +79,11 @@ export const registerLatticeRoutes = (app: FastifyInstance, channelDeps?: { rout
76
79
  Params: { assistantId: string; threadId: string };
77
80
  }>("/api/assistants/:assistantId/threads/:threadId/abort", runController.abortRun);
78
81
 
82
+ // Recover agent execution after a crash or manual abort
83
+ app.post<{
84
+ Params: { assistantId: string; threadId: string };
85
+ }>("/api/assistants/:assistantId/threads/:threadId/recover", runController.recoverRun);
86
+
79
87
  // app.get<{
80
88
  // Params: { id: string };
81
89
  // }>("/api/runs/:id", runController.getRun);
@@ -158,6 +166,19 @@ export const registerLatticeRoutes = (app: FastifyInstance, channelDeps?: { rout
158
166
  Params: { id: string };
159
167
  }>("/api/assistants/:id", assistantController.deleteAssistant);
160
168
 
169
+ // Personal Assistant routes
170
+ app.post<{ Body: any }>("/api/personal-assistant", paController.createPersonalAssistant);
171
+ app.get("/api/personal-assistant", paController.getPersonalAssistant);
172
+ app.delete("/api/personal-assistant", paController.deletePersonalAssistant);
173
+
174
+ // Task CRUD routes
175
+ app.get("/api/tasks", tasksController.listTasks);
176
+ app.get("/api/tasks/:id", tasksController.getTask);
177
+ app.post("/api/tasks", tasksController.createTask);
178
+ app.put("/api/tasks/:id", tasksController.updateTask);
179
+ app.delete("/api/tasks/:id", tasksController.deleteTask);
180
+ app.patch("/api/tasks/:id/complete", tasksController.completeTask);
181
+
161
182
  // 图表路由
162
183
  app.get<{
163
184
  Params: { assistantId: string };
@@ -361,6 +382,8 @@ export const registerLatticeRoutes = (app: FastifyInstance, channelDeps?: { rout
361
382
 
362
383
  registerChannelInstallationRoutes(app);
363
384
 
385
+ registerMenuItemRoutes(app);
386
+
364
387
  if (channelDeps) {
365
388
  registerChannelBindingRoutes(app);
366
389
  }
@@ -0,0 +1,10 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ import * as menuController from "../controllers/menu-items";
3
+
4
+ export function registerMenuItemRoutes(app: FastifyInstance): void {
5
+ app.get("/api/menu-items", menuController.getMenuItemList);
6
+ app.post("/api/menu-items", menuController.createMenuItem);
7
+ app.get("/api/menu-items/:id", menuController.getMenuItem);
8
+ app.put("/api/menu-items/:id", menuController.updateMenuItem);
9
+ app.delete("/api/menu-items/:id", menuController.deleteMenuItem);
10
+ }
@@ -54,7 +54,6 @@ const handleAgentTask = async (
54
54
  eventBus.publish(callback_event, {
55
55
  success: true,
56
56
  state: evt.state,
57
- config: { assistant_id, thread_id, tenant_id },
58
57
  });
59
58
 
60
59
  if (main_thread_id && main_tenant_id) {
@@ -63,6 +62,8 @@ const handleAgentTask = async (
63
62
  assistant_id: main_assistant_id ?? assistant_id,
64
63
  thread_id: main_thread_id,
65
64
  tenant_id: main_tenant_id,
65
+ workspace_id: runConfig?.workspaceId,
66
+ project_id: runConfig?.projectId,
66
67
  });
67
68
  if (mainAgent) {
68
69
  const messages = evt.state?.values?.messages;
@@ -93,7 +94,6 @@ const handleAgentTask = async (
93
94
  eventBus.publish(callback_event, {
94
95
  success: true,
95
96
  state: evt.state,
96
- config: { assistant_id, thread_id, tenant_id },
97
97
  });
98
98
  })
99
99
  }
@@ -127,7 +127,6 @@ const handleAgentTask = async (
127
127
  eventBus.publish(callback_event, {
128
128
  success: false,
129
129
  error: error instanceof Error ? error.message : String(error),
130
- config: { assistant_id, thread_id, tenant_id },
131
130
  });
132
131
  }
133
132
 
@@ -334,11 +333,6 @@ export class AgentTaskConsumer {
334
333
  eventBus.publish(taskRequest.callback_event, {
335
334
  success: false,
336
335
  error: error instanceof Error ? error.message : String(error),
337
- config: {
338
- assistant_id: taskRequest.assistant_id,
339
- thread_id: taskRequest.thread_id,
340
- tenant_id: taskRequest["x-tenant-id"],
341
- },
342
336
  });
343
337
  }
344
338
  });
@@ -1,23 +0,0 @@
1
- import { MessageChunkTypes } from "@axiom-lattice/protocols";
2
- import { aggregateLarkReply } from "../aggregator";
3
-
4
- describe("aggregateLarkReply", () => {
5
- it("joins ai chunks for the requested message id", () => {
6
- const result = aggregateLarkReply("msg-1", [
7
- { type: MessageChunkTypes.AI, data: { id: "msg-1", content: "Hel" } },
8
- { type: MessageChunkTypes.AI, data: { id: "msg-2", content: "skip" } },
9
- { type: MessageChunkTypes.TOOL, data: { id: "msg-1", content: "ignore" } },
10
- { type: MessageChunkTypes.AI, data: { id: "msg-1", content: "lo" } },
11
- ]);
12
-
13
- expect(result).toBe("Hello");
14
- });
15
-
16
- it("returns an empty string when no ai content exists for the message", () => {
17
- const result = aggregateLarkReply("msg-1", [
18
- { type: MessageChunkTypes.MESSAGE_COMPLETED, data: { id: "msg-1" } },
19
- ]);
20
-
21
- expect(result).toBe("");
22
- });
23
- });
@@ -1,16 +0,0 @@
1
- import type { MessageChunk } from "@axiom-lattice/protocols";
2
- import { MessageChunkTypes } from "@axiom-lattice/protocols";
3
-
4
- export function aggregateLarkReply(
5
- messageId: string,
6
- chunks: MessageChunk[],
7
- ): string {
8
- return chunks
9
- .filter(
10
- (chunk) =>
11
- chunk.type === MessageChunkTypes.AI && chunk.data.id === messageId,
12
- )
13
- .map((chunk) => chunk.data.content || "")
14
- .join("")
15
- .trim();
16
- }
@@ -1,44 +0,0 @@
1
- import type { LarkIngressConfig } from "./types";
2
-
3
- export function loadLarkIngressConfig(): LarkIngressConfig {
4
- return {
5
- enabled: process.env.LARK_ENABLED !== "false",
6
- appId: process.env.LARK_APP_ID || "",
7
- appSecret: process.env.LARK_APP_SECRET || "",
8
- verificationToken: process.env.LARK_VERIFICATION_TOKEN,
9
- encryptKey: process.env.LARK_ENCRYPT_KEY,
10
- tenantId: process.env.LARK_TENANT_ID || "default",
11
- assistantId: process.env.LARK_ASSISTANT_ID || "default_agent",
12
- workspaceId: process.env.LARK_WORKSPACE_ID,
13
- projectId: process.env.LARK_PROJECT_ID,
14
- mappingMode:
15
- (process.env.LARK_MAPPING_MODE as "user" | "group" | "hybrid") ||
16
- "hybrid",
17
- };
18
- }
19
-
20
- export function isLarkIngressEnabled(config: LarkIngressConfig): boolean {
21
- return config.enabled;
22
- }
23
-
24
- export function assertLarkIngressConfig(config: LarkIngressConfig): void {
25
- if (!config.enabled) {
26
- return;
27
- }
28
-
29
- if (!config.appId) {
30
- throw new Error("LARK_APP_ID is required when Lark ingress is enabled");
31
- }
32
-
33
- if (!config.appSecret) {
34
- throw new Error("LARK_APP_SECRET is required when Lark ingress is enabled");
35
- }
36
-
37
- if (!config.tenantId) {
38
- throw new Error("LARK_TENANT_ID is required when Lark ingress is enabled");
39
- }
40
-
41
- if (!config.assistantId) {
42
- throw new Error("LARK_ASSISTANT_ID is required when Lark ingress is enabled");
43
- }
44
- }
@@ -1,37 +0,0 @@
1
- import { agentInstanceManager } from "@axiom-lattice/core";
2
- import { MessageChunkTypes } from "@axiom-lattice/protocols";
3
- import { aggregateLarkReply } from "./aggregator";
4
-
5
- export async function runAgentAndCollectLarkReply(input: {
6
- tenantId: string;
7
- assistantId: string;
8
- threadId: string;
9
- text: string;
10
- workspaceId?: string;
11
- projectId?: string;
12
- }): Promise<string> {
13
- const agent = agentInstanceManager.getAgent({
14
- tenant_id: input.tenantId,
15
- assistant_id: input.assistantId,
16
- thread_id: input.threadId,
17
- workspace_id: input.workspaceId,
18
- project_id: input.projectId,
19
- });
20
-
21
- const result = await agent.addMessage({
22
- input: {
23
- message: input.text,
24
- },
25
- });
26
-
27
- const chunks = [];
28
- const stream = agent.chunkStream(result.messageId, [
29
- MessageChunkTypes.MESSAGE_COMPLETED,
30
- ]);
31
-
32
- for await (const chunk of stream) {
33
- chunks.push(chunk);
34
- }
35
-
36
- return aggregateLarkReply(result.messageId, chunks);
37
- }