@axiom-lattice/gateway 2.1.92 → 2.1.94

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";
@@ -125,6 +125,11 @@ app.addHook("preHandler", async (request, reply) => {
125
125
  // Public routes don't require authentication
126
126
  if (PUBLIC_ROUTES.some((r) => request.url === r)) return;
127
127
 
128
+ // Allow direct URL access for iframe endpoints (viewfile, downloadfile)
129
+ // These carry tenant context via query params and are accessed by iframes
130
+ const urlPath = request.url.split("?")[0];
131
+ if (urlPath.includes("/viewfile") || urlPath.includes("/downloadfile")) return;
132
+
128
133
  return reply.status(401).send({
129
134
  success: false,
130
135
  error: "Unauthorized - Missing or invalid token",
@@ -277,17 +282,22 @@ const start = async (config?: LatticeGatewayConfig) => {
277
282
  // Access via: request.server.loggerLattice or app.loggerLattice
278
283
  app.decorate("loggerLattice", loggerLattice);
279
284
 
280
- // Channel infrastructure: use stores from StoreLatticeManager (defaults: InMemory).
281
- // Projects can override with PG stores via registerStoreLattice before start().
285
+ // Channel infrastructure: use stores from StoreLatticeManager.
286
+ // Applications (e.g. deep_research) register stores via configureStores()
287
+ // before calling startAsHttpEndpoint(). If no stores are registered,
288
+ // channel features are gracefully skipped.
282
289
  let channelDeps: { router: MessageRouter; installationStore: ChannelInstallationStore } | undefined;
290
+ const adapterRegistry = new ChannelAdapterRegistry();
291
+ adapterRegistry.register(larkChannelAdapter);
283
292
  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);
293
+ const { getStoreLattice: getStore } = await import("@axiom-lattice/core");
294
+ let bindingStore: BindingRegistry;
295
+ let installationStore: ChannelInstallationStore;
288
296
 
289
- const adapterRegistry = new ChannelAdapterRegistry();
290
- adapterRegistry.register(larkChannelAdapter);
297
+ bindingStore = getStore("default", "channelBinding").store as BindingRegistry;
298
+ installationStore = getStore("default", "channelInstallation").store as ChannelInstallationStore;
299
+
300
+ setBindingRegistry(bindingStore);
291
301
 
292
302
  const router = new MessageRouter({
293
303
  middlewares: [
@@ -305,7 +315,7 @@ const start = async (config?: LatticeGatewayConfig) => {
305
315
 
306
316
  // Initialize A2A API key store
307
317
  try {
308
- const a2aKeyStore = getStoreLattice("default", "a2aApiKey").store as import("@axiom-lattice/protocols").A2AApiKeyStore;
318
+ const a2aKeyStore = getStore("default", "a2aApiKey").store as import("@axiom-lattice/protocols").A2AApiKeyStore;
309
319
  const a2a = await import("./routes/a2a");
310
320
  a2a.setA2AKeyStore(a2aKeyStore);
311
321
  await a2a.refreshStoreKeyMap();
@@ -313,8 +323,19 @@ const start = async (config?: LatticeGatewayConfig) => {
313
323
  } catch {
314
324
  // A2A key store optional — env-based keys still work
315
325
  }
326
+ } catch (err) {
327
+ logger.warn("Channel infrastructure unavailable", {
328
+ error: err instanceof Error ? err.message : String(err),
329
+ });
330
+ }
331
+
332
+ // Initialize MenuRegistry for custom menu items
333
+ try {
334
+ const menuStore = getStoreLattice("default", "menu").store;
335
+ setMenuRegistry(menuStore);
336
+ logger.info("Menu registry initialized");
316
337
  } catch {
317
- // Stores not registeredchannel routes will be skipped gracefully
338
+ // Menu store optionalcustom menus won't work without it
318
339
  }
319
340
 
320
341
  // Register all routes (channel routes only active if channelDeps is set)
@@ -326,6 +347,21 @@ const start = async (config?: LatticeGatewayConfig) => {
326
347
  logger.info("Registered sandbox manager from env configuration");
327
348
  }
328
349
 
350
+ // Connect all enabled channel installations
351
+ if (channelDeps && process.env.CHANNELS_ENABLED !== "false") {
352
+ const { connectAllChannels } = await import("@axiom-lattice/core");
353
+ try {
354
+ await connectAllChannels(
355
+ (channel) => adapterRegistry.get(channel),
356
+ { deps: { router: channelDeps.router } },
357
+ );
358
+ } catch (err) {
359
+ logger.error("Failed to start channel connections", {
360
+ error: err instanceof Error ? err.message : String(err),
361
+ });
362
+ }
363
+ }
364
+
329
365
  const target_port = config?.port || Number(process.env.PORT) || 4001;
330
366
 
331
367
  await app.listen({ port: target_port, host: "0.0.0.0" });
@@ -348,15 +384,14 @@ const start = async (config?: LatticeGatewayConfig) => {
348
384
  }
349
385
  }
350
386
 
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
- }
387
+ // Restore agent instances with pending messages (fire-and-forget to avoid blocking startup)
388
+ agentInstanceManager.restore()
389
+ .then((stats) => {
390
+ logger.info(`Agent recovery complete: ${stats.restored} threads restored, ${stats.errors} errors`);
391
+ })
392
+ .catch((error) => {
393
+ logger.error("Agent recovery failed", { error });
394
+ });
360
395
  } catch (err) {
361
396
  logger.error("Server start failed", { error: err });
362
397
  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
- }