@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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +12 -0
- package/dist/index.js +116 -61
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +116 -62
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/controllers/auth.ts +21 -27
- package/src/controllers/run.ts +15 -3
- package/src/controllers/workflow-tracking.ts +84 -0
- package/src/index.ts +41 -48
- package/src/routes/index.ts +5 -0
package/package.json
CHANGED
package/src/controllers/auth.ts
CHANGED
|
@@ -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
|
|
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
|
|
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));
|
package/src/controllers/run.ts
CHANGED
|
@@ -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" });
|
package/src/routes/index.ts
CHANGED
|
@@ -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);
|