@axiom-lattice/gateway 2.1.38 → 2.1.39

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.
@@ -6,7 +6,7 @@ import type {
6
6
  CreateAssistantRequest,
7
7
  } from "@axiom-lattice/protocols";
8
8
  import { randomUUID } from "crypto";
9
- import { AgentConfig, getAllAgentConfigs, eventBus } from "@axiom-lattice/core";
9
+ import { AgentConfig, agentLatticeManager, eventBus } from "@axiom-lattice/core";
10
10
 
11
11
  /**
12
12
  * Assistant Controller
@@ -16,12 +16,28 @@ import { AgentConfig, getAllAgentConfigs, eventBus } from "@axiom-lattice/core";
16
16
  * CUD operations only work on stored assistants
17
17
  */
18
18
 
19
+ /**
20
+ * Get tenant ID from authenticated user context
21
+ * Falls back to request headers for backward compatibility
22
+ */
23
+ function getTenantId(request: FastifyRequest): string {
24
+ // First try to get from authenticated user context
25
+ const userTenantId = (request as any).user?.tenantId;
26
+ if (userTenantId) {
27
+ return userTenantId;
28
+ }
29
+
30
+ // Fallback to request headers for backward compatibility
31
+ return (request.headers["x-tenant-id"] as string) || "default";
32
+ }
33
+
19
34
  /**
20
35
  * Convert AgentConfig to Assistant format
21
36
  */
22
37
  function convertAgentConfigToAssistant(config: AgentConfig): Assistant {
23
38
  return {
24
39
  id: config.key,
40
+ tenantId: "default", // Code-configured agents belong to default tenant
25
41
  name: config.name,
26
42
  description: config.description,
27
43
  graphDefinition: config, // Store the full config as graphDefinition
@@ -68,16 +84,18 @@ export async function getAssistantList(
68
84
  request: FastifyRequest,
69
85
  reply: FastifyReply
70
86
  ): Promise<AssistantListResponse> {
71
- // Get code-configured agents
72
- const agentConfigs = await getAllAgentConfigs();
87
+ const tenantId = getTenantId(request);
88
+
89
+ // Get code-configured agents for the tenant
90
+ const agentConfigs = await agentLatticeManager.getAllAgentConfigsByTenant(tenantId);
73
91
  const codeConfiguredAssistants = agentConfigs.map(
74
92
  convertAgentConfigToAssistant
75
93
  );
76
94
 
77
- // Get stored assistants
95
+ // Get stored assistants for the tenant
78
96
  const storeLattice = getStoreLattice("default", "assistant");
79
97
  const assistantStore = storeLattice.store;
80
- const storedAssistants = await assistantStore.getAllAssistants();
98
+ const storedAssistants = await assistantStore.getAllAssistants(tenantId);
81
99
 
82
100
  // Merge both sources, stored assistants take precedence if ID conflicts
83
101
  const assistantMap = new Map<string, Assistant>();
@@ -113,16 +131,16 @@ export async function getAssistant(
113
131
  reply: FastifyReply
114
132
  ): Promise<AssistantResponse> {
115
133
  const { id } = request.params;
134
+ const tenantId = getTenantId(request);
116
135
 
117
136
  // First check stored assistants
118
137
  const storeLattice = getStoreLattice("default", "assistant");
119
138
  const assistantStore = storeLattice.store;
120
- let assistant = await assistantStore.getAssistantById(id);
139
+ let assistant = await assistantStore.getAssistantById(tenantId, id);
121
140
 
122
- // If not found in store, check code-configured agents
141
+ // If not found in store, check code-configured agents for the tenant
123
142
  if (!assistant) {
124
- const agentConfigs = await getAllAgentConfigs();
125
- const agentConfig = agentConfigs.find((config) => config.key === id);
143
+ const agentConfig = await agentLatticeManager.getAgentConfigWithTenant(tenantId, id);
126
144
  if (agentConfig) {
127
145
  assistant = convertAgentConfigToAssistant(agentConfig);
128
146
  }
@@ -146,6 +164,7 @@ export async function getAssistant(
146
164
  * Upsert assistant - create if not exists in store, update if exists
147
165
  */
148
166
  async function upsertAssistant(
167
+ tenantId: string,
149
168
  id: string,
150
169
  data: CreateAssistantRequest | AssistantUpdateBody,
151
170
  reply: FastifyReply,
@@ -154,18 +173,18 @@ async function upsertAssistant(
154
173
  const storeLattice = getStoreLattice("default", "assistant");
155
174
  const assistantStore = storeLattice.store;
156
175
 
157
- const exists = await assistantStore.hasAssistant(id);
176
+ const exists = await assistantStore.hasAssistant(tenantId, id);
158
177
 
159
178
  let assistant: Assistant | null;
160
179
  if (exists) {
161
- assistant = await assistantStore.updateAssistant(id, data);
180
+ assistant = await assistantStore.updateAssistant(tenantId, id, data);
162
181
  if (!assistant) {
163
182
  return reply.status(500).send({
164
183
  success: false,
165
184
  message: "Failed to update assistant",
166
185
  });
167
186
  }
168
- eventBus.publish("assistant:updated", { id: assistant.id, name: assistant.name });
187
+ eventBus.publish("assistant:updated", { id: assistant.id, name: assistant.name, tenantId });
169
188
  return {
170
189
  success: true,
171
190
  message: "Updated assistant",
@@ -183,8 +202,8 @@ async function upsertAssistant(
183
202
  }
184
203
  }
185
204
 
186
- assistant = await assistantStore.createAssistant(id, data as CreateAssistantRequest);
187
- eventBus.publish("assistant:created", { id: assistant.id, name: assistant.name });
205
+ assistant = await assistantStore.createAssistant(tenantId, id, data as CreateAssistantRequest);
206
+ eventBus.publish("assistant:created", { id: assistant.id, name: assistant.name, tenantId });
188
207
  return reply.status(201).send({
189
208
  success: true,
190
209
  message: "Created assistant",
@@ -199,6 +218,7 @@ export async function createAssistant(
199
218
  request: FastifyRequest<{ Body: CreateAssistantRequest & { id?: string } }>,
200
219
  reply: FastifyReply
201
220
  ): Promise<AssistantResponse> {
221
+ const tenantId = getTenantId(request);
202
222
  const data = request.body;
203
223
 
204
224
  if (!data.name || !data.graphDefinition) {
@@ -209,7 +229,7 @@ export async function createAssistant(
209
229
  }
210
230
 
211
231
  const id = data.id ?? randomUUID();
212
- return upsertAssistant(id, data, reply, true);
232
+ return upsertAssistant(tenantId, id, data, reply, true);
213
233
  }
214
234
 
215
235
  /**
@@ -222,10 +242,11 @@ export async function updateAssistant(
222
242
  }>,
223
243
  reply: FastifyReply
224
244
  ): Promise<AssistantResponse> {
245
+ const tenantId = getTenantId(request);
225
246
  const { id } = request.params;
226
247
  const updates = request.body;
227
248
 
228
- return upsertAssistant(id, updates, reply, false);
249
+ return upsertAssistant(tenantId, id, updates, reply, false);
229
250
  }
230
251
 
231
252
  /**
@@ -237,31 +258,32 @@ export async function deleteAssistant(
237
258
  request: FastifyRequest<{ Params: { id: string } }>,
238
259
  reply: FastifyReply
239
260
  ): Promise<{ success: boolean; message: string }> {
261
+ const tenantId = getTenantId(request);
240
262
  const { id } = request.params;
241
263
 
242
264
  const storeLattice = getStoreLattice("default", "assistant");
243
265
  const assistantStore = storeLattice.store;
244
266
 
245
- const agentConfigs = await getAllAgentConfigs();
246
- const isCodeConfigured = agentConfigs.some((config) => config.key === id);
267
+ const agentConfig = await agentLatticeManager.getAgentConfigWithTenant(tenantId, id);
268
+ const isCodeConfigured = !!agentConfig;
247
269
 
248
270
  if (isCodeConfigured) {
249
- const exists = await assistantStore.hasAssistant(id);
271
+ const exists = await assistantStore.hasAssistant(tenantId, id);
250
272
  if (!exists) {
251
273
  return reply.status(404).send({
252
274
  success: false,
253
275
  message: "Assistant not found (code-configured assistants cannot be deleted from code, only from store)",
254
276
  });
255
277
  }
256
- await assistantStore.deleteAssistant(id);
257
- eventBus.publish("assistant:deleted", { id });
278
+ await assistantStore.deleteAssistant(tenantId, id);
279
+ eventBus.publish("assistant:deleted", { id, tenantId });
258
280
  return {
259
281
  success: true,
260
282
  message: "Deleted assistant from store (code-configured registration remains)",
261
283
  };
262
284
  }
263
285
 
264
- const exists = await assistantStore.hasAssistant(id);
286
+ const exists = await assistantStore.hasAssistant(tenantId, id);
265
287
  if (!exists) {
266
288
  return reply.status(404).send({
267
289
  success: false,
@@ -269,7 +291,7 @@ export async function deleteAssistant(
269
291
  });
270
292
  }
271
293
 
272
- const deleted = await assistantStore.deleteAssistant(id);
294
+ const deleted = await assistantStore.deleteAssistant(tenantId, id);
273
295
 
274
296
  if (!deleted) {
275
297
  return reply.status(500).send({
@@ -278,7 +300,7 @@ export async function deleteAssistant(
278
300
  });
279
301
  }
280
302
 
281
- eventBus.publish("assistant:deleted", { id });
303
+ eventBus.publish("assistant:deleted", { id, tenantId });
282
304
 
283
305
  return {
284
306
  success: true,
@@ -297,9 +319,10 @@ export const getAgentGraph = async (
297
319
  ) => {
298
320
  try {
299
321
  const { assistantId } = request.params;
322
+ const tenant_id = getTenantId(request);
300
323
 
301
324
  // Call drawing service to get image data
302
- const imageData = await draw_graph(assistantId);
325
+ const imageData = await draw_graph(assistantId, tenant_id);
303
326
 
304
327
  // Set response header and return image data
305
328
  reply.header("Content-Type", "application/json").send({
@@ -18,9 +18,17 @@ import { randomUUID } from "crypto";
18
18
  */
19
19
 
20
20
  /**
21
- * Get tenant ID from request headers
21
+ * Get tenant ID from authenticated user context
22
+ * Falls back to request headers for backward compatibility
22
23
  */
23
24
  function getTenantId(request: FastifyRequest): string {
25
+ // First try to get from authenticated user context
26
+ const userTenantId = (request as any).user?.tenantId;
27
+ if (userTenantId) {
28
+ return userTenantId;
29
+ }
30
+
31
+ // Fallback to request headers for backward compatibility
24
32
  return (request.headers["x-tenant-id"] as string) || "default";
25
33
  }
26
34
 
@@ -165,7 +173,7 @@ export async function createDatabaseConfig(
165
173
 
166
174
  // Auto-register to SqlDatabaseManager
167
175
  try {
168
- sqlDatabaseManager.registerDatabase(config.key, config.config);
176
+ sqlDatabaseManager.registerDatabase(tenantId, config.key, config.config);
169
177
  } catch (error) {
170
178
  console.warn("Failed to auto-register database:", error);
171
179
  }
@@ -221,7 +229,7 @@ export async function updateDatabaseConfig(
221
229
  // Re-register to SqlDatabaseManager if config changed
222
230
  if (updates.config) {
223
231
  try {
224
- sqlDatabaseManager.registerDatabase(updated.key, updated.config);
232
+ sqlDatabaseManager.registerDatabase(tenantId, updated.key, updated.config);
225
233
  } catch (error) {
226
234
  console.warn("Failed to re-register database:", error);
227
235
  }
@@ -290,8 +298,8 @@ export async function deleteDatabaseConfig(
290
298
 
291
299
  // Remove from SqlDatabaseManager
292
300
  try {
293
- if (sqlDatabaseManager.hasDatabase(configKey)) {
294
- await sqlDatabaseManager.removeDatabase(configKey);
301
+ if (sqlDatabaseManager.hasDatabase(tenantId, configKey)) {
302
+ await sqlDatabaseManager.removeDatabase(tenantId, configKey);
295
303
  }
296
304
  } catch (error) {
297
305
  console.warn("Failed to remove from SqlDatabaseManager:", error);
@@ -335,10 +343,10 @@ export async function testDatabaseConnection(
335
343
 
336
344
  // Register temporarily for testing
337
345
  const testKey = `__test_${key}_${Date.now()}`;
338
- sqlDatabaseManager.registerDatabase(testKey, config.config);
346
+ sqlDatabaseManager.registerDatabase(tenantId, testKey, config.config);
339
347
 
340
348
  const startTime = Date.now();
341
- const db = sqlDatabaseManager.getDatabase(testKey);
349
+ const db = sqlDatabaseManager.getDatabase(tenantId, testKey);
342
350
 
343
351
  try {
344
352
  // Try to connect and list tables
@@ -351,7 +359,7 @@ export async function testDatabaseConnection(
351
359
  await db.disconnect();
352
360
 
353
361
  // Cleanup
354
- await sqlDatabaseManager.removeDatabase(testKey);
362
+ await sqlDatabaseManager.removeDatabase(tenantId, testKey);
355
363
 
356
364
  return {
357
365
  success: true,
@@ -365,7 +373,7 @@ export async function testDatabaseConnection(
365
373
  // Cleanup on error
366
374
  try {
367
375
  await db.disconnect();
368
- await sqlDatabaseManager.removeDatabase(testKey);
376
+ await sqlDatabaseManager.removeDatabase(tenantId, testKey);
369
377
  } catch {}
370
378
 
371
379
  return {
@@ -20,9 +20,17 @@ import type {
20
20
  import { randomUUID } from "crypto";
21
21
 
22
22
  /**
23
- * Get tenant ID from request headers
23
+ * Get tenant ID from authenticated user context
24
+ * Falls back to request headers for backward compatibility
24
25
  */
25
26
  function getTenantId(request: FastifyRequest): string {
27
+ // First try to get from authenticated user context
28
+ const userTenantId = (request as any).user?.tenantId;
29
+ if (userTenantId) {
30
+ return userTenantId;
31
+ }
32
+
33
+ // Fallback to request headers for backward compatibility
26
34
  return (request.headers["x-tenant-id"] as string) || "default";
27
35
  }
28
36
 
@@ -155,9 +155,12 @@ export const getAgentState = async (
155
155
  return;
156
156
  }
157
157
 
158
+ const tenant_id = request.headers["x-tenant-id"] as string;
159
+
158
160
  const result = await agent_state({
159
161
  assistant_id: assistantId,
160
162
  thread_id: thread_id,
163
+ tenant_id: tenant_id,
161
164
  });
162
165
 
163
166
  if (!result) {
@@ -22,9 +22,17 @@ import { randomUUID } from "crypto";
22
22
  */
23
23
 
24
24
  /**
25
- * Get tenant ID from request headers
25
+ * Get tenant ID from authenticated user context
26
+ * Falls back to request headers for backward compatibility
26
27
  */
27
28
  function getTenantId(request: FastifyRequest): string {
29
+ // First try to get from authenticated user context
30
+ const userTenantId = (request as any).user?.tenantId;
31
+ if (userTenantId) {
32
+ return userTenantId;
33
+ }
34
+
35
+ // Fallback to request headers for backward compatibility
28
36
  return (request.headers["x-tenant-id"] as string) || "default";
29
37
  }
30
38
 
@@ -124,8 +132,18 @@ interface DatasourceMetricsResponse {
124
132
  searchKeywords: string[];
125
133
  registered: boolean;
126
134
  }>;
135
+ tables?: Array<{
136
+ tableName: string;
137
+ displayName: string;
138
+ docType: string;
139
+ docTypeEn: string;
140
+ mainTable?: string;
141
+ lineTable?: string;
142
+ columnCount: number;
143
+ shortDesc: string;
144
+ }>;
127
145
  };
128
- details: Array<{
146
+ metricsDetails: Array<{
129
147
  datasourceId: number;
130
148
  metricName: string;
131
149
  displayName: string;
@@ -176,6 +194,22 @@ interface DatasourceMetricsResponse {
176
194
  humanReadableExplanation: string;
177
195
  };
178
196
  }>;
197
+ tablesDetails?: Array<{
198
+ tableName: string;
199
+ docType: string;
200
+ docTypeEn: string;
201
+ objTypeCode?: number;
202
+ mainTable?: string;
203
+ lineTable?: string;
204
+ selectSql: string;
205
+ columns: Array<{
206
+ name: string;
207
+ label: string;
208
+ description?: string;
209
+ type?: string;
210
+ example?: string;
211
+ } | null>;
212
+ }>;
179
213
  };
180
214
  }
181
215
 
@@ -326,7 +360,7 @@ export async function createMetricsServerConfig(
326
360
 
327
361
  // Auto-register to MetricsServerManager
328
362
  try {
329
- metricsServerManager.registerServer(config.key, config.config);
363
+ metricsServerManager.registerServer(tenantId, config.key, config.config);
330
364
  } catch (error) {
331
365
  console.warn("Failed to auto-register metrics server:", error);
332
366
  }
@@ -395,7 +429,7 @@ export async function updateMetricsServerConfig(
395
429
 
396
430
  if (updates.config) {
397
431
  try {
398
- metricsServerManager.registerServer(updated.key, updated.config);
432
+ metricsServerManager.registerServer(tenantId, updated.key, updated.config);
399
433
  } catch (error) {
400
434
  console.warn("Failed to re-register metrics server:", error);
401
435
  }
@@ -460,8 +494,8 @@ export async function deleteMetricsServerConfig(
460
494
 
461
495
  // Remove from MetricsServerManager
462
496
  try {
463
- if (metricsServerManager.hasServer(configKey)) {
464
- metricsServerManager.removeServer(configKey);
497
+ if (metricsServerManager.hasServer(tenantId, configKey)) {
498
+ metricsServerManager.removeServer(tenantId, configKey);
465
499
  }
466
500
  } catch (error) {
467
501
  console.warn("Failed to remove from MetricsServerManager:", error);
@@ -505,14 +539,14 @@ export async function testMetricsServerConnection(
505
539
 
506
540
  // Register temporarily for testing
507
541
  const testKey = `__test_${key}_${Date.now()}`;
508
- metricsServerManager.registerServer(testKey, config.config);
542
+ metricsServerManager.registerServer(tenantId, testKey, config.config);
509
543
 
510
544
  try {
511
- const client = metricsServerManager.getClient(testKey);
545
+ const client = metricsServerManager.getClient(tenantId, testKey);
512
546
  const result = await client.testConnection();
513
547
 
514
548
  // Cleanup
515
- metricsServerManager.removeServer(testKey);
549
+ metricsServerManager.removeServer(tenantId, testKey);
516
550
 
517
551
  return {
518
552
  success: true,
@@ -522,7 +556,7 @@ export async function testMetricsServerConnection(
522
556
  } catch (error) {
523
557
  // Cleanup on error
524
558
  try {
525
- metricsServerManager.removeServer(testKey);
559
+ metricsServerManager.removeServer(tenantId, testKey);
526
560
  } catch {}
527
561
 
528
562
  return {
@@ -571,11 +605,11 @@ export async function listAvailableMetrics(
571
605
  }
572
606
 
573
607
  // Ensure server is registered
574
- if (!metricsServerManager.hasServer(key)) {
575
- metricsServerManager.registerServer(key, config.config);
608
+ if (!metricsServerManager.hasServer(tenantId, key)) {
609
+ metricsServerManager.registerServer(tenantId, key, config.config);
576
610
  }
577
611
 
578
- const client = metricsServerManager.getClient(key);
612
+ const client = metricsServerManager.getClient(tenantId, key);
579
613
  const metrics = await client.listMetrics();
580
614
 
581
615
  return {
@@ -637,11 +671,11 @@ export async function queryMetricsData(
637
671
  }
638
672
 
639
673
  // Ensure server is registered
640
- if (!metricsServerManager.hasServer(key)) {
641
- metricsServerManager.registerServer(key, config.config);
674
+ if (!metricsServerManager.hasServer(tenantId, key)) {
675
+ metricsServerManager.registerServer(tenantId, key, config.config);
642
676
  }
643
677
 
644
- const client = metricsServerManager.getClient(key);
678
+ const client = metricsServerManager.getClient(tenantId, key);
645
679
  const result = await client.queryMetricData(metricName, {
646
680
  startTime,
647
681
  endTime,
@@ -822,14 +856,42 @@ export async function querySemanticMetrics(
822
856
  const client = new SemanticMetricsClient(semanticConfig);
823
857
  const result = await client.semanticQuery(body);
824
858
 
859
+ // Transform SemanticMetricsQueryResponse to response format
860
+ // The response contains results array with metric data
861
+ const allDataPoints: Array<{
862
+ timestamp?: number;
863
+ value: number;
864
+ metricName?: string;
865
+ labels?: Record<string, string>;
866
+ }> = [];
867
+ const metricNames: string[] = [];
868
+
869
+ for (const metricResult of result.results) {
870
+ metricNames.push(metricResult.metricName);
871
+ for (const row of metricResult.rows) {
872
+ allDataPoints.push({
873
+ timestamp: row.timestamp ? new Date(row.timestamp as string).getTime() : undefined,
874
+ value: typeof row.value === 'number' ? row.value : 0,
875
+ metricName: metricResult.metricName,
876
+ labels: Object.fromEntries(
877
+ Object.entries(row).filter(([k]) => k !== 'value' && k !== 'timestamp')
878
+ .map(([k, v]) => [k, String(v)])
879
+ ),
880
+ });
881
+ }
882
+ }
883
+
825
884
  return {
826
885
  success: true,
827
886
  message: "Semantic query executed successfully",
828
887
  data: {
829
888
  datasourceId: result.datasourceId,
830
- metrics: result.metrics,
831
- dataPoints: result.dataPoints,
832
- metadata: result.metadata,
889
+ metrics: metricNames,
890
+ dataPoints: allDataPoints,
891
+ metadata: {
892
+ rowCount: allDataPoints.length,
893
+ queryTimeMs: result.totalExecutionTimeMs,
894
+ },
833
895
  },
834
896
  };
835
897
  } catch (error) {
@@ -5,8 +5,41 @@ import type { LLMConfig } from "@axiom-lattice/protocols";
5
5
  /**
6
6
  * Models Controller
7
7
  * Handles model lattice registration and management
8
+ * Models are tenant-isolated using key prefix: {tenantId}:{modelKey}
8
9
  */
9
10
 
11
+ /**
12
+ * Get tenant ID from request headers or user context
13
+ */
14
+ function getTenantId(request: FastifyRequest): string {
15
+ // First try to get from authenticated user context
16
+ const userTenantId = (request as any).user?.tenantId;
17
+ if (userTenantId) {
18
+ return userTenantId;
19
+ }
20
+
21
+ // Fallback to request headers for backward compatibility
22
+ return (request.headers["x-tenant-id"] as string) || "default";
23
+ }
24
+
25
+ /**
26
+ * Get tenant-scoped model key
27
+ */
28
+ function getTenantModelKey(tenantId: string, modelKey: string): string {
29
+ return `${tenantId}:${modelKey}`;
30
+ }
31
+
32
+ /**
33
+ * Extract model key from tenant-scoped key
34
+ */
35
+ function extractModelKey(tenantId: string, tenantModelKey: string): string {
36
+ const prefix = `${tenantId}:`;
37
+ if (tenantModelKey.startsWith(prefix)) {
38
+ return tenantModelKey.substring(prefix.length);
39
+ }
40
+ return tenantModelKey;
41
+ }
42
+
10
43
  interface ModelConfig {
11
44
  key: string;
12
45
  model: string;
@@ -26,6 +59,12 @@ interface UpdateModelsRequest {
26
59
  };
27
60
  }
28
61
 
62
+ interface UpdateModelsRequest {
63
+ Body: {
64
+ models: ModelConfig[];
65
+ };
66
+ }
67
+
29
68
  /**
30
69
  * Get all registered models
31
70
  */
@@ -42,6 +42,16 @@ export const createRun = async (
42
42
 
43
43
  // 如果请求streaming,则agent_stream
44
44
  if (streaming) {
45
+ // 先检查agent是否存在(在hijack之前)
46
+ const agentExists = await agentService.checkAgentExists(tenant_id, assistant_id);
47
+ if (!agentExists) {
48
+ reply.status(404).send({
49
+ success: false,
50
+ error: `Agent ${assistant_id} not found for tenant ${tenant_id}`,
51
+ });
52
+ return;
53
+ }
54
+
45
55
  // 开始运行
46
56
  const stream = await agentService.agent_stream({
47
57
  assistant_id: assistant_id,
@@ -67,11 +77,24 @@ export const createRun = async (
67
77
  });
68
78
 
69
79
  try {
80
+ let chunkCount = 0;
70
81
  for await (const chunk of stream) {
71
- reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`);
82
+ chunkCount++;
83
+ const success = reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`);
84
+ if (!success) {
85
+ await new Promise(resolve => reply.raw.once('drain', resolve));
86
+ }
72
87
  }
73
- } catch (error) {
74
- console.error("Stream processing error:", error);
88
+ } catch (error: any) {
89
+ // Send error as SSE event before closing (following MessageChunk format)
90
+ const errorEvent = {
91
+ type: 'error',
92
+ data: {
93
+ id: v4(),
94
+ content: error.message || 'Stream processing error'
95
+ }
96
+ };
97
+ reply.raw.write(`data: ${JSON.stringify(errorEvent)}\n\n`);
75
98
  } finally {
76
99
  reply.raw.end();
77
100
  }
@@ -138,11 +161,18 @@ export const resumeStream = async (
138
161
 
139
162
  // Stream the chunks to the client
140
163
  for await (const chunk of stream) {
141
- // console.log("resume stream raw.write", chunk);
142
164
  reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`);
143
165
  }
144
- } catch (error) {
145
- console.error("Resume stream processing error:", error);
166
+ } catch (error: any) {
167
+ // Send error as SSE event before closing (following MessageChunk format)
168
+ const errorEvent = {
169
+ type: 'error',
170
+ data: {
171
+ id: v4(),
172
+ content: error.message || 'Resume stream processing error'
173
+ }
174
+ };
175
+ reply.raw.write(`data: ${JSON.stringify(errorEvent)}\n\n`);
146
176
  } finally {
147
177
  reply.raw.end();
148
178
  }
@@ -53,8 +53,9 @@ export function registerSandboxProxyRoutes(app: FastifyInstance): void {
53
53
  async (request, reply) => {
54
54
  console.log("[Sandbox Upload] Route matched:", request.url);
55
55
  const { assistantId, threadId } = request.params;
56
+ const tenantId = (request.headers["x-tenant-id"] as string) || "default";
56
57
 
57
- const isolatedLevel = sandboxService.getFilesystemIsolatedLevel(assistantId);
58
+ const isolatedLevel = sandboxService.getFilesystemIsolatedLevel(tenantId, assistantId);
58
59
  if (!isolatedLevel) {
59
60
  return reply.status(500).send({ error: "Assistant sandbox config not found" });
60
61
  }
@@ -65,7 +66,7 @@ export function registerSandboxProxyRoutes(app: FastifyInstance): void {
65
66
  isolatedLevel
66
67
  );
67
68
 
68
- const sandboxManager = getSandBoxManager("default")
69
+ const sandboxManager = getSandBoxManager()
69
70
  const sandbox = await sandboxManager.createSandbox(sandboxName)
70
71
 
71
72
  try {
@@ -116,12 +117,13 @@ export function registerSandboxProxyRoutes(app: FastifyInstance): void {
116
117
  async (request, reply) => {
117
118
  const { assistantId, threadId } = request.params;
118
119
  const { path: filePath } = request.query;
120
+ const tenantId = (request.headers["x-tenant-id"] as string) || "default";
119
121
 
120
122
  if (!filePath || typeof filePath !== "string") {
121
123
  return reply.status(400).send({ error: "Query parameter 'path' is required" });
122
124
  }
123
125
 
124
- const isolatedLevel = sandboxService.getFilesystemIsolatedLevel(assistantId);
126
+ const isolatedLevel = sandboxService.getFilesystemIsolatedLevel(tenantId, assistantId);
125
127
  if (!isolatedLevel) {
126
128
  return reply.status(500).send({ error: "Assistant filesystem isolated level not found" });
127
129
  }
@@ -132,7 +134,7 @@ export function registerSandboxProxyRoutes(app: FastifyInstance): void {
132
134
  isolatedLevel
133
135
  );
134
136
 
135
- const sandboxManager = getSandBoxManager("default");
137
+ const sandboxManager = getSandBoxManager();
136
138
  const sandbox = await sandboxManager.createSandbox(sandboxName);
137
139
 
138
140
  try {