@axiom-lattice/gateway 2.1.49 → 2.1.50

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.49",
3
+ "version": "2.1.50",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -38,8 +38,8 @@
38
38
  "pg": "^8.11.0",
39
39
  "redis": "^5.0.1",
40
40
  "uuid": "^9.0.1",
41
- "@axiom-lattice/core": "2.1.43",
42
- "@axiom-lattice/pg-stores": "1.0.33",
41
+ "@axiom-lattice/core": "2.1.44",
42
+ "@axiom-lattice/pg-stores": "1.0.34",
43
43
  "@axiom-lattice/protocols": "2.1.23",
44
44
  "@axiom-lattice/queue-redis": "1.0.22"
45
45
  },
@@ -29,6 +29,11 @@ interface AssignTenantRequest {
29
29
  role?: UserTenantRole;
30
30
  }
31
31
 
32
+ interface ChangePasswordRequest {
33
+ currentPassword: string;
34
+ newPassword: string;
35
+ }
36
+
32
37
  export interface AuthConfig {
33
38
  autoApproveUsers: boolean;
34
39
  allowTenantRegistration: boolean;
@@ -391,6 +396,71 @@ export class AuthController {
391
396
  }
392
397
  return btoa(JSON.stringify(payload));
393
398
  }
399
+
400
+ async changePassword(
401
+ request: FastifyRequest<{ Body: ChangePasswordRequest }>,
402
+ reply: FastifyReply
403
+ ) {
404
+ const userId = (request as any).user?.id;
405
+ const { currentPassword, newPassword } = request.body;
406
+
407
+ if (!userId) {
408
+ return reply.status(401).send({
409
+ success: false,
410
+ error: "Unauthorized",
411
+ });
412
+ }
413
+
414
+ try {
415
+ const user = await this.userStore.getUserById(userId);
416
+ if (!user) {
417
+ return reply.status(404).send({
418
+ success: false,
419
+ error: "User not found",
420
+ });
421
+ }
422
+
423
+ // Verify current password
424
+ const isValidPassword = await this.verifyPassword(
425
+ currentPassword,
426
+ user.metadata?.passwordHash || ""
427
+ );
428
+
429
+ if (!isValidPassword) {
430
+ return reply.status(401).send({
431
+ success: false,
432
+ error: "Current password is incorrect",
433
+ });
434
+ }
435
+
436
+ // Validate new password length
437
+ if (newPassword.length < 6) {
438
+ return reply.status(400).send({
439
+ success: false,
440
+ error: "New password must be at least 6 characters",
441
+ });
442
+ }
443
+
444
+ // Update password
445
+ await this.userStore.updateUser(userId, {
446
+ metadata: {
447
+ ...user.metadata,
448
+ passwordHash: await this.hashPassword(newPassword),
449
+ },
450
+ });
451
+
452
+ return reply.send({
453
+ success: true,
454
+ message: "Password changed successfully",
455
+ });
456
+ } catch (error) {
457
+ console.error("Change password error:", error);
458
+ return reply.status(500).send({
459
+ success: false,
460
+ error: "Failed to change password",
461
+ });
462
+ }
463
+ }
394
464
  }
395
465
 
396
466
  export function registerAuthRoutes(
@@ -440,4 +510,7 @@ export function registerAuthRoutes(
440
510
  app.post("/api/auth/approve", { preHandler: authHook }, (req, res) => controller.approveUser(req as any, res));
441
511
  app.get("/api/auth/pending", { preHandler: authHook }, (req, res) => controller.listPendingUsers(req, res));
442
512
  app.post("/api/auth/assign-tenant", { preHandler: authHook }, (req, res) => controller.assignTenant(req as any, res));
513
+ app.post("/api/auth/change-password", { preHandler: authHook },
514
+ (req, res) => controller.changePassword(req as any, res)
515
+ );
443
516
  }
@@ -6,6 +6,7 @@ import {
6
6
  ThreadStatus,
7
7
  agentLatticeManager,
8
8
  agentInstanceManager,
9
+ QueueMode,
9
10
  } from "@axiom-lattice/core";
10
11
  import { MessageChunkTypes } from "@axiom-lattice/protocols";
11
12
 
@@ -26,10 +27,12 @@ export const createRun = async (
26
27
  const {
27
28
  assistant_id,
28
29
  thread_id,
30
+ message_id,
29
31
  command,
30
32
  streaming,
31
33
  background,
32
34
  custom_run_config,
35
+ mode,
33
36
  ...input
34
37
  } = request.body as CreateRunRequest;
35
38
 
@@ -72,11 +75,16 @@ export const createRun = async (
72
75
  try {
73
76
 
74
77
  // Execute agent with streaming
78
+ // Only pass id if message_id is provided (for backward compatibility)
79
+ const messageInput = message_id
80
+ ? { ...input, id: message_id }
81
+ : input;
82
+
75
83
  const result = await agent.addMessage({
76
- input,
84
+ input: messageInput,
77
85
  command,
78
86
  custom_run_config,
79
- });
87
+ }, mode as QueueMode);
80
88
 
81
89
  // const agentStatus = await agent.getRunStatus()
82
90
  // console.log(agentStatus)
@@ -1,228 +1,66 @@
1
- // import { FastifyRequest, FastifyReply } from "fastify";
2
- // import { QueueMode } from "@axiom-lattice/core";
3
-
4
-
5
- // /**
6
- // * GET /api/threads/:thread_id/status
7
- // * Query single thread status
8
- // */
9
- // export async function getThreadStatusHandler(
10
- // request: FastifyRequest,
11
- // reply: FastifyReply
12
- // ) {
13
- // try {
14
- // const { thread_id } = request.params as { thread_id: string };
15
- // const tenant_id = request.headers["x-tenant-id"] as string;
16
-
17
- // if (!tenant_id) {
18
- // return reply.code(400).send({ error: "Missing x-tenant-id header" });
19
- // }
20
-
21
- // const state = lifecycleManager.getThreadStatus(tenant_id, thread_id);
22
-
23
- // if (!state) {
24
- // return reply.code(404).send({
25
- // error: "Thread not found",
26
- // threadId: thread_id,
27
- // });
28
- // }
29
-
30
- // return reply.send(state);
31
- // } catch (error) {
32
- // console.error("Error getting thread status:", error);
33
- // return reply.code(500).send({
34
- // error: "Internal server error",
35
- // message: error instanceof Error ? error.message : String(error),
36
- // });
37
- // }
38
- // }
39
-
40
- // /**
41
- // * GET /api/assistants/:assistant_id/threads/status
42
- // * Query all threads status for an agent
43
- // */
44
- // export async function getAgentThreadsHandler(
45
- // request: FastifyRequest,
46
- // reply: FastifyReply
47
- // ) {
48
- // try {
49
- // const { assistant_id } = request.params as { assistant_id: string };
50
- // const tenant_id = request.headers["x-tenant-id"] as string;
51
-
52
- // if (!tenant_id) {
53
- // return reply.code(400).send({ error: "Missing x-tenant-id header" });
54
- // }
55
-
56
- // const threads = lifecycleManager.getAgentThreads(tenant_id, assistant_id);
57
-
58
- // return reply.send({
59
- // assistantId: assistant_id,
60
- // threads,
61
- // count: threads.length,
62
- // });
63
- // } catch (error) {
64
- // console.error("Error getting agent threads:", error);
65
- // return reply.code(500).send({
66
- // error: "Internal server error",
67
- // message: error instanceof Error ? error.message : String(error),
68
- // });
69
- // }
70
- // }
71
-
72
- // /**
73
- // * GET /api/threads/active
74
- // * Query all active threads (BUSY + INTERRUPTED)
75
- // */
76
- // export async function getActiveThreadsHandler(
77
- // request: FastifyRequest,
78
- // reply: FastifyReply
79
- // ) {
80
- // try {
81
- // const tenant_id = request.headers["x-tenant-id"] as string;
82
-
83
- // if (!tenant_id) {
84
- // return reply.code(400).send({ error: "Missing x-tenant-id header" });
85
- // }
86
-
87
- // const threads = lifecycleManager.getActiveThreads(tenant_id);
88
-
89
- // return reply.send({
90
- // threads,
91
- // count: threads.length,
92
- // });
93
- // } catch (error) {
94
- // console.error("Error getting active threads:", error);
95
- // return reply.code(500).send({
96
- // error: "Internal server error",
97
- // message: error instanceof Error ? error.message : String(error),
98
- // });
99
- // }
100
- // }
101
-
102
- // /**
103
- // * POST /api/threads/:thread_id/messages
104
- // * Add message to queue or execute immediately (steer mode)
105
- // */
106
- // export async function addMessageHandler(
107
- // request: FastifyRequest,
108
- // reply: FastifyReply
109
- // ) {
110
- // try {
111
- // const { thread_id } = request.params as { thread_id: string };
112
- // const tenant_id = request.headers["x-tenant-id"] as string;
113
- // const { assistant_id, content, mode } = request.body as {
114
- // assistant_id: string;
115
- // content: any;
116
- // mode?: QueueMode;
117
- // };
118
-
119
- // if (!tenant_id) {
120
- // return reply.code(400).send({ error: "Missing x-tenant-id header" });
121
- // }
122
-
123
- // if (!assistant_id || !content) {
124
- // return reply.code(400).send({ error: "Missing assistant_id or content" });
125
- // }
126
-
127
- // const result = await lifecycleManager.addMessage(
128
- // tenant_id,
129
- // assistant_id,
130
- // thread_id,
131
- // content,
132
- // mode
133
- // );
134
-
135
- // return reply.send({
136
- // success: true,
137
- // threadId: thread_id,
138
- // ...result,
139
- // });
140
- // } catch (error) {
141
- // console.error("Error adding message:", error);
142
- // return reply.code(500).send({
143
- // error: "Internal server error",
144
- // message: error instanceof Error ? error.message : String(error),
145
- // });
146
- // }
147
- // }
148
-
149
- // /**
150
- // * DELETE /api/threads/:thread_id/messages/:message_id
151
- // * Remove pending message
152
- // */
153
- // export async function removeMessageHandler(
154
- // request: FastifyRequest,
155
- // reply: FastifyReply
156
- // ) {
157
- // try {
158
- // const { thread_id, message_id } = request.params as {
159
- // thread_id: string;
160
- // message_id: string;
161
- // };
162
- // const tenant_id = request.headers["x-tenant-id"] as string;
163
-
164
- // if (!tenant_id) {
165
- // return reply.code(400).send({ error: "Missing x-tenant-id header" });
166
- // }
167
-
168
- // const success = await lifecycleManager.removePendingMessage(
169
- // tenant_id,
170
- // thread_id,
171
- // message_id
172
- // );
173
-
174
- // if (!success) {
175
- // return reply.code(404).send({
176
- // error: "Message not found",
177
- // messageId: message_id,
178
- // });
179
- // }
180
-
181
- // return reply.send({
182
- // success: true,
183
- // messageId: message_id,
184
- // });
185
- // } catch (error) {
186
- // console.error("Error removing message:", error);
187
- // return reply.code(500).send({
188
- // error: "Internal server error",
189
- // message: error instanceof Error ? error.message : String(error),
190
- // });
191
- // }
192
- // }
193
-
194
- // /**
195
- // * PUT /api/threads/:thread_id/queue-config
196
- // * Update queue configuration
197
- // */
198
- // export async function updateQueueConfigHandler(
199
- // request: FastifyRequest,
200
- // reply: FastifyReply
201
- // ) {
202
- // try {
203
- // const { thread_id } = request.params as { thread_id: string };
204
- // const tenant_id = request.headers["x-tenant-id"] as string;
205
- // const config = request.body as {
206
- // mode?: QueueMode;
207
- // maxSize?: number;
208
- // };
209
-
210
- // if (!tenant_id) {
211
- // return reply.code(400).send({ error: "Missing x-tenant-id header" });
212
- // }
213
-
214
- // await lifecycleManager.setQueueConfig(tenant_id, thread_id, config);
215
-
216
- // return reply.send({
217
- // success: true,
218
- // threadId: thread_id,
219
- // config,
220
- // });
221
- // } catch (error) {
222
- // console.error("Error updating queue config:", error);
223
- // return reply.code(500).send({
224
- // error: "Internal server error",
225
- // message: error instanceof Error ? error.message : String(error),
226
- // });
227
- // }
228
- // }
1
+ import { FastifyRequest, FastifyReply } from "fastify";
2
+ import { agentInstanceManager } from "@axiom-lattice/core";
3
+
4
+ /**
5
+ * DELETE /api/assistants/:assistant_id/threads/:thread_id/pending-messages/:message_id
6
+ * Remove pending message from queue
7
+ */
8
+ export async function removePendingMessageHandler(
9
+ request: FastifyRequest,
10
+ reply: FastifyReply
11
+ ) {
12
+ try {
13
+ const { assistant_id, thread_id, message_id } = request.params as {
14
+ assistant_id: string;
15
+ thread_id: string;
16
+ message_id: string;
17
+ };
18
+ const tenant_id = request.headers["x-tenant-id"] as string;
19
+ const workspace_id = request.headers["x-workspace-id"] as string;
20
+ const project_id = request.headers["x-project-id"] as string;
21
+
22
+ if (!tenant_id) {
23
+ return reply.code(400).send({ error: "Missing x-tenant-id header" });
24
+ }
25
+
26
+ if (!assistant_id) {
27
+ return reply.code(400).send({ error: "Missing assistant_id parameter" });
28
+ }
29
+
30
+ // Get agent instance
31
+ const agent = agentInstanceManager.getAgent({
32
+ assistant_id,
33
+ thread_id,
34
+ tenant_id,
35
+ workspace_id,
36
+ project_id,
37
+ });
38
+
39
+ if (!agent) {
40
+ return reply.code(404).send({
41
+ error: "Thread not found",
42
+ threadId: thread_id,
43
+ });
44
+ }
45
+
46
+ const success = await agent.removePendingMessage(message_id);
47
+
48
+ if (!success) {
49
+ return reply.code(404).send({
50
+ error: "Message not found",
51
+ messageId: message_id,
52
+ });
53
+ }
54
+
55
+ return reply.send({
56
+ success: true,
57
+ messageId: message_id,
58
+ });
59
+ } catch (error) {
60
+ console.error("Error removing pending message:", error);
61
+ return reply.code(500).send({
62
+ error: "Internal server error",
63
+ message: error instanceof Error ? error.message : String(error),
64
+ });
65
+ }
66
+ }
package/src/index.ts CHANGED
@@ -69,6 +69,22 @@ const app = fastify({
69
69
  bodyLimit: Number(process.env.BODY_LIMIT) || 50 * 1024 * 1024, // Default 50MB, configurable via BODY_LIMIT env var
70
70
  });
71
71
 
72
+ // Custom content type parser to handle DELETE requests with Content-Type but no body
73
+ // This prevents "Body cannot be empty when content-type is set to 'application/json'" error
74
+ app.addContentTypeParser('application/json', { parseAs: 'string' }, function (request, body, done) {
75
+ // For DELETE requests or empty body, return empty object
76
+ if (request.method === 'DELETE' || !body || body.length === 0) {
77
+ done(null, {});
78
+ return;
79
+ }
80
+ try {
81
+ const json = JSON.parse(body as string);
82
+ done(null, json);
83
+ } catch (err) {
84
+ done(err as Error, undefined);
85
+ }
86
+ });
87
+
72
88
 
73
89
  // Add custom logging hooks
74
90
  app.addHook("onRequest", (request, reply, done) => {
@@ -93,15 +109,6 @@ app.addHook("onRequest", (request, reply, done) => {
93
109
  done();
94
110
  });
95
111
 
96
- // Fix for DELETE requests with Content-Type: application/json header
97
- // Some clients send Content-Type header even for DELETE requests without body
98
- app.addHook("onRequest", async (request, reply) => {
99
- if (request.method === "DELETE" && request.headers["content-type"]) {
100
- // Remove content-type header for DELETE requests to avoid body parsing errors
101
- delete request.headers["content-type"];
102
- }
103
- });
104
-
105
112
  app.addHook("onResponse", (request, reply, done) => {
106
113
  // Convert headers to strings (Fastify headers can be string | string[])
107
114
  const getHeaderValue = (
@@ -30,6 +30,7 @@ import {
30
30
  getSandboxUrlSchema,
31
31
  dataQuerySchema,
32
32
  } from "../schemas";
33
+ import { removePendingMessageHandler } from "../controllers/thread_status";
33
34
  import { registerSandboxProxyRoutes } from "../controllers/sandbox";
34
35
  import { registerWorkspaceRoutes } from "../controllers/workspace";
35
36
  import { registerDatabaseConfigRoutes } from "../controllers/database-configs";
@@ -346,4 +347,12 @@ export const registerLatticeRoutes = (app: FastifyInstance): void => {
346
347
  // app.post("/api/threads/:thread_id/messages", addMessageHandler);
347
348
  // app.delete("/api/threads/:thread_id/messages/:message_id", removeMessageHandler);
348
349
  // app.put("/api/threads/:thread_id/queue-config", updateQueueConfigHandler);
350
+
351
+ // Delete pending message route
352
+ app.delete<{
353
+ Params: { assistant_id: string; thread_id: string; message_id: string };
354
+ }>(
355
+ "/api/assistants/:assistant_id/threads/:thread_id/pending-messages/:message_id",
356
+ removePendingMessageHandler
357
+ );
349
358
  };
@@ -77,10 +77,12 @@ export interface CreateRunRequest {
77
77
  thread_id: string;
78
78
  assistant_id: string;
79
79
  message: any;
80
+ message_id?: string;
80
81
  command?: any;
81
82
  streaming?: boolean;
82
83
  background?: boolean;
83
84
  custom_run_config?: Record<string, any>;
85
+ mode?: 'collect' | 'followup' | 'steer';
84
86
  }
85
87
 
86
88
  // 添加消息请求类型