@axiom-lattice/gateway 2.1.65 → 2.1.67

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiom-lattice/gateway",
3
- "version": "2.1.65",
3
+ "version": "2.1.67",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -463,6 +463,23 @@ export class AuthController {
463
463
  }
464
464
  }
465
465
 
466
+ export function extractUserFromAuthHeader(
467
+ authHeader: string | undefined
468
+ ): { id: string; tenantId?: string } | undefined {
469
+ if (!authHeader?.startsWith("Bearer ")) return undefined;
470
+ const token = authHeader.substring(7);
471
+ try {
472
+ const payload = JSON.parse(atob(token));
473
+ if (payload.exp && payload.exp < Date.now()) return undefined;
474
+ return {
475
+ id: payload.userId,
476
+ tenantId: payload.tenantId,
477
+ };
478
+ } catch {
479
+ return undefined;
480
+ }
481
+ }
482
+
466
483
  export function registerAuthRoutes(
467
484
  app: FastifyInstance,
468
485
  config?: Partial<AuthConfig>
@@ -470,37 +487,14 @@ export function registerAuthRoutes(
470
487
  const controller = new AuthController(config);
471
488
 
472
489
  const authHook = async (request: FastifyRequest, reply: FastifyReply) => {
473
- const authHeader = request.headers.authorization;
474
-
475
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
476
- return reply.status(401).send({
477
- success: false,
478
- error: "Unauthorized - Missing or invalid token",
479
- });
480
- }
481
-
482
- const token = authHeader.substring(7);
483
-
484
- try {
485
- const payload = JSON.parse(atob(token));
486
-
487
- if (payload.exp && payload.exp < Date.now()) {
488
- return reply.status(401).send({
489
- success: false,
490
- error: "Unauthorized - Token expired",
491
- });
492
- }
493
-
494
- (request as any).user = {
495
- id: payload.userId,
496
- tenantId: payload.tenantId,
497
- };
498
- } catch {
490
+ const user = extractUserFromAuthHeader(request.headers.authorization);
491
+ if (!user) {
499
492
  return reply.status(401).send({
500
493
  success: false,
501
- error: "Unauthorized - Invalid token",
494
+ error: "Unauthorized",
502
495
  });
503
496
  }
497
+ (request as any).user = user;
504
498
  };
505
499
 
506
500
  app.post("/api/auth/register", controller.register.bind(controller));
@@ -10,6 +10,12 @@ import {
10
10
  } from "@axiom-lattice/core";
11
11
  import { MessageChunkTypes } from "@axiom-lattice/protocols";
12
12
 
13
+ function getUserId(request: FastifyRequest): string | undefined {
14
+ const authUser = (request as any).user;
15
+ if (authUser?.id) return authUser.id;
16
+ return (request.headers["x-user-id"] as string) || undefined;
17
+ }
18
+
13
19
  interface ResumeStreamRequest {
14
20
  thread_id: string;
15
21
  assistant_id: string
@@ -40,6 +46,12 @@ export const createRun = async (
40
46
  const workspace_id = request.headers["x-workspace-id"] as string;
41
47
  const project_id = request.headers["x-project-id"] as string;
42
48
  const x_request_id = (request.headers["x-request-id"] as string) || v4();
49
+ const user_id = getUserId(request);
50
+
51
+ // Merge user_id into custom_run_config for downstream propagation
52
+ const mergedConfig = user_id
53
+ ? { ...custom_run_config, user_id }
54
+ : custom_run_config;
43
55
 
44
56
  // Validate request
45
57
  if (!assistant_id) {
@@ -57,7 +69,7 @@ export const createRun = async (
57
69
  tenant_id,
58
70
  workspace_id,
59
71
  project_id,
60
- custom_run_config,
72
+ custom_run_config: mergedConfig,
61
73
  });
62
74
 
63
75
 
@@ -83,7 +95,7 @@ export const createRun = async (
83
95
  const result = await agent.addMessage({
84
96
  input: messageInput,
85
97
  command,
86
- custom_run_config,
98
+ custom_run_config: mergedConfig,
87
99
  }, mode as QueueMode);
88
100
 
89
101
  // const agentStatus = await agent.getRunStatus()
@@ -124,7 +136,7 @@ export const createRun = async (
124
136
  const result = await agent.invoke({
125
137
  input: { message: msg, ...restInputNonStream },
126
138
  command,
127
- custom_run_config,
139
+ custom_run_config: mergedConfig,
128
140
  });
129
141
  reply.status(200).send({
130
142
  success: true,
@@ -383,3 +383,87 @@ export async function getRunTasks(
383
383
  return reply.status(500).send({ success: false, message: "Failed to retrieve user tasks" });
384
384
  }
385
385
  }
386
+
387
+ // 简化的 Inbox 任务回复接口
388
+ interface ReplyInboxRequest {
389
+ runId: string;
390
+ answers: Array<{
391
+ questionIndex: number;
392
+ selectedOptions: string[];
393
+ }>;
394
+ }
395
+
396
+ export async function replyInboxTask(
397
+ request: FastifyRequest<{ Body: ReplyInboxRequest }>,
398
+ reply: FastifyReply
399
+ ): Promise<void> {
400
+ const { runId, answers } = request.body;
401
+ const tenantId = getTenantId(request);
402
+
403
+ try {
404
+ // 1. 获取 WorkflowRun 信息
405
+ const store = getTrackingStore();
406
+ if (!store) {
407
+ return reply.status(404).send({
408
+ success: false,
409
+ message: "No workflow tracking store configured"
410
+ });
411
+ }
412
+
413
+ const run = await store.getWorkflowRun(runId);
414
+ if (!run) {
415
+ return reply.status(404).send({
416
+ success: false,
417
+ message: "Workflow run not found"
418
+ });
419
+ }
420
+
421
+ // 2. 获取 Agent 实例
422
+ const agent = agentInstanceManager.getAgent({
423
+ assistant_id: run.assistantId,
424
+ thread_id: run.threadId,
425
+ tenant_id: tenantId,
426
+ });
427
+
428
+ // 3. 提交 resume 请求并确认没有报错
429
+ // addMessage 会触发 streaming 执行,但不会等待完整 workflow 完成
430
+ try {
431
+ await agent.addMessage({
432
+ input: { message: "Clarification answers submitted" },
433
+ command: {
434
+ resume: {
435
+ action: "submit",
436
+ data: { answers },
437
+ message: "Clarification answers submitted",
438
+ },
439
+ },
440
+ });
441
+ } catch (error) {
442
+ // resume 提交失败(如 thread 不在 interrupted 状态)
443
+ return reply.status(400).send({
444
+ success: false,
445
+ message: "Failed to resume workflow",
446
+ error: error instanceof Error ? error.message : String(error),
447
+ });
448
+ }
449
+
450
+ // 4. 立即返回成功(不等待 agent 执行完成)
451
+ return reply.status(200).send({
452
+ success: true,
453
+ message: "Resume request submitted successfully",
454
+ data: {
455
+ runId: run.id,
456
+ assistantId: run.assistantId,
457
+ threadId: run.threadId,
458
+ status: "resuming",
459
+ },
460
+ });
461
+
462
+ } catch (error) {
463
+ request.log.error(error, "Failed to reply inbox task");
464
+ return reply.status(500).send({
465
+ success: false,
466
+ message: "Failed to submit resume request"
467
+ });
468
+ }
469
+ }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import staticPlugin from "@fastify/static";
7
7
  import path from "path";
8
8
  import { fileURLToPath } from "url";
9
9
  import { registerLatticeRoutes } from "./routes";
10
+ import { extractUserFromAuthHeader } from "./controllers/auth";
10
11
  import { configureSwagger } from "./swagger";
11
12
  import {
12
13
  setQueueServiceType,
@@ -20,7 +21,6 @@ import {
20
21
  sandboxLatticeManager,
21
22
  sqlDatabaseManager,
22
23
  getStoreLattice,
23
- storeLatticeManager,
24
24
  agentInstanceManager,
25
25
  createSandboxProvider,
26
26
  type CreateSandboxProviderConfig,
@@ -90,21 +90,49 @@ app.addContentTypeParser('application/json', { parseAs: 'string' }, function (re
90
90
  });
91
91
 
92
92
 
93
+ // Helper: extract single header value (Fastify headers can be string | string[])
94
+ const getHeaderValue = (
95
+ header: string | string[] | undefined
96
+ ): string | undefined => {
97
+ if (Array.isArray(header)) {
98
+ return header[0];
99
+ }
100
+ return header;
101
+ };
102
+
103
+ // Routes that don't require authentication even when AUTH_REQUIRED=true
104
+ const PUBLIC_ROUTES = ["/api/auth/login", "/api/auth/register", "/health"];
105
+
106
+ // Global auth preHandler: extract user from Authorization header
107
+ // When AUTH_REQUIRED=true, reject unauthenticated requests (except public routes)
108
+ app.addHook("preHandler", async (request, reply) => {
109
+ const user = extractUserFromAuthHeader(request.headers.authorization);
110
+ if (user) {
111
+ (request as any).user = user;
112
+ return;
113
+ }
114
+
115
+ const authRequired = process.env.AUTH_REQUIRED === "true";
116
+ if (!authRequired) return;
117
+
118
+ // OPTIONS preflight should always pass
119
+ if (request.method === "OPTIONS") return;
120
+
121
+ // Public routes don't require authentication
122
+ if (PUBLIC_ROUTES.some((r) => request.url === r)) return;
123
+
124
+ return reply.status(401).send({
125
+ success: false,
126
+ error: "Unauthorized - Missing or invalid token",
127
+ });
128
+ });
129
+
93
130
  // Add custom logging hooks
94
131
  app.addHook("onRequest", (request, reply, done) => {
95
- // Convert headers to strings (Fastify headers can be string | string[])
96
- const getHeaderValue = (
97
- header: string | string[] | undefined
98
- ): string | undefined => {
99
- if (Array.isArray(header)) {
100
- return header[0];
101
- }
102
- return header;
103
- };
104
-
105
132
  const context = {
106
133
  "x-tenant-id": getHeaderValue(request.headers["x-tenant-id"]),
107
134
  "x-request-id": getHeaderValue(request.headers["x-request-id"]),
135
+ "x-user-id": getHeaderValue(request.headers["x-user-id"]),
108
136
  };
109
137
  // Update logger context for this request
110
138
  if (loggerLattice.updateContext) {
@@ -114,19 +142,10 @@ app.addHook("onRequest", (request, reply, done) => {
114
142
  });
115
143
 
116
144
  app.addHook("onResponse", (request, reply, done) => {
117
- // Convert headers to strings (Fastify headers can be string | string[])
118
- const getHeaderValue = (
119
- header: string | string[] | undefined
120
- ): string | undefined => {
121
- if (Array.isArray(header)) {
122
- return header[0];
123
- }
124
- return header;
125
- };
126
-
127
145
  const context = {
128
146
  "x-tenant-id": getHeaderValue(request.headers["x-tenant-id"]),
129
147
  "x-request-id": getHeaderValue(request.headers["x-request-id"]),
148
+ "x-user-id": getHeaderValue(request.headers["x-user-id"]),
130
149
  };
131
150
  //loggerLattice.info(`${request.method} ${request.url} - ${reply.statusCode}`);
132
151
  done();
@@ -158,19 +177,10 @@ app.register(staticPlugin, {
158
177
 
159
178
  // Error handler
160
179
  app.setErrorHandler((error, request, reply) => {
161
- // Convert headers to strings (Fastify headers can be string | string[])
162
- const getHeaderValue = (
163
- header: string | string[] | undefined
164
- ): string | undefined => {
165
- if (Array.isArray(header)) {
166
- return header[0];
167
- }
168
- return header;
169
- };
170
-
171
180
  const context = {
172
181
  "x-tenant-id": getHeaderValue(request.headers["x-tenant-id"]),
173
182
  "x-request-id": getHeaderValue(request.headers["x-request-id"]),
183
+ "x-user-id": getHeaderValue(request.headers["x-user-id"]),
174
184
  };
175
185
  logger.error(
176
186
  `请求错误: ${request.method} ${request.url} error:${error.message}`,
@@ -274,23 +284,6 @@ const start = async (config?: LatticeGatewayConfig) => {
274
284
  logger.info("Registered sandbox manager from env configuration");
275
285
  }
276
286
 
277
- // Swap workflow tracking to PostgreSQL if DATABASE_URL is set
278
- if (process.env.DATABASE_URL) {
279
- try {
280
- const { PostgreSQLWorkflowTrackingStore } = await import("@axiom-lattice/pg-stores");
281
- const pgStore = new PostgreSQLWorkflowTrackingStore({
282
- poolConfig: process.env.DATABASE_URL,
283
- });
284
- if (storeLatticeManager.hasLattice("default", "workflowTracking")) {
285
- storeLatticeManager.removeLattice("default", "workflowTracking");
286
- }
287
- storeLatticeManager.registerLattice("default", "workflowTracking", pgStore);
288
- logger.info("Workflow tracking store switched to PostgreSQL");
289
- } catch (error) {
290
- logger.warn("Failed to switch workflow tracking to PostgreSQL, keeping in-memory: " + (error instanceof Error ? error.message : String(error)));
291
- }
292
- }
293
-
294
287
  const target_port = config?.port || Number(process.env.PORT) || 4001;
295
288
 
296
289
  await app.listen({ port: target_port, host: "0.0.0.0" });
@@ -395,6 +395,11 @@ export const registerLatticeRoutes = (app: FastifyInstance): void => {
395
395
  Params: { runId: string };
396
396
  }>("/api/workflows/runs/:runId/tasks", workflowTrackingController.getRunTasks);
397
397
 
398
+ // 简化的 Inbox 任务回复接口(后台 streaming 执行,立即返回)
399
+ app.post<{
400
+ Body: { runId: string; answers: Array<{ questionIndex: number; selectedOptions: string[] }> };
401
+ }>("/api/workflows/inbox/reply", workflowTrackingController.replyInboxTask);
402
+
398
403
  // // Thread 状态查询路由
399
404
  // app.get("/api/threads/:thread_id/status", getThreadStatusHandler);
400
405
  // app.get("/api/assistants/:assistant_id/threads/status", getAgentThreadsHandler);