@axiom-lattice/gateway 2.1.39 → 2.1.41

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.
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect, beforeEach, jest } from "@jest/globals";
2
+ import { FastifyRequest, FastifyReply } from "fastify";
3
+ import { executeDataQuery } from "../controllers/data-query";
4
+
5
+ // Mock @axiom-lattice/core
6
+ jest.mock("@axiom-lattice/core", () => ({
7
+ getStoreLattice: jest.fn(),
8
+ metricsServerManager: {
9
+ hasServer: jest.fn(),
10
+ registerServer: jest.fn(),
11
+ },
12
+ SemanticMetricsClient: jest.fn(),
13
+ }));
14
+
15
+ describe("Data Query Controller", () => {
16
+ let mockRequest: Partial<FastifyRequest>;
17
+ let mockReply: Partial<FastifyReply>;
18
+
19
+ beforeEach(() => {
20
+ mockRequest = {
21
+ headers: { "x-tenant-id": "test-tenant" },
22
+ body: {},
23
+ };
24
+ mockReply = {
25
+ code: jest.fn().mockReturnThis(),
26
+ };
27
+ });
28
+
29
+ describe("Request validation", () => {
30
+ it("should return 400 if neither serverKey nor datasourceId is provided", async () => {
31
+ mockRequest.body = {};
32
+
33
+ const result = await executeDataQuery(
34
+ mockRequest as FastifyRequest,
35
+ mockReply as FastifyReply
36
+ );
37
+
38
+ expect(mockReply.code).toHaveBeenCalledWith(400);
39
+ expect(result.success).toBe(false);
40
+ expect(result.message).toContain("serverKey or datasourceId");
41
+ });
42
+
43
+ it("should return 400 if neither metrics nor customSql is provided", async () => {
44
+ mockRequest.body = {
45
+ serverKey: "test-server",
46
+ datasourceId: "1",
47
+ };
48
+
49
+ const result = await executeDataQuery(
50
+ mockRequest as FastifyRequest,
51
+ mockReply as FastifyReply
52
+ );
53
+
54
+ expect(mockReply.code).toHaveBeenCalledWith(400);
55
+ expect(result.success).toBe(false);
56
+ expect(result.message).toContain("metrics (for semantic query) or customSql (for SQL query)");
57
+ });
58
+
59
+ it("should return 400 if both metrics and customSql are provided", async () => {
60
+ mockRequest.body = {
61
+ serverKey: "test-server",
62
+ datasourceId: "1",
63
+ metrics: ["test_metric"],
64
+ customSql: "SELECT * FROM test",
65
+ };
66
+
67
+ const result = await executeDataQuery(
68
+ mockRequest as FastifyRequest,
69
+ mockReply as FastifyReply
70
+ );
71
+
72
+ expect(mockReply.code).toHaveBeenCalledWith(400);
73
+ expect(result.success).toBe(false);
74
+ expect(result.message).toContain("Cannot provide both");
75
+ });
76
+ });
77
+ });
@@ -0,0 +1,236 @@
1
+ import { FastifyRequest, FastifyReply } from "fastify";
2
+ import {
3
+ getStoreLattice,
4
+ metricsServerManager,
5
+ SemanticMetricsClient,
6
+ } from "@axiom-lattice/core";
7
+ import type {
8
+ MetricsServerConfigStore,
9
+ SemanticMetricsServerConfig,
10
+ SemanticMetricsFilter,
11
+ } from "@axiom-lattice/protocols";
12
+
13
+ /**
14
+ * Get tenant ID from request headers
15
+ */
16
+ function getTenantId(request: FastifyRequest): string {
17
+ return (request.headers["x-tenant-id"] as string) || "default";
18
+ }
19
+
20
+ /**
21
+ * Data query request body
22
+ */
23
+ interface DataQueryRequest {
24
+ serverKey?: string;
25
+ datasourceId?: string;
26
+ metrics?: string[];
27
+ groupBy?: string[];
28
+ filters?: Array<{
29
+ dimension: string;
30
+ operator: string;
31
+ values: (string | number | boolean)[];
32
+ }>;
33
+ customSql?: string;
34
+ params?: Record<string, string | number | boolean>;
35
+ limit?: number;
36
+ }
37
+
38
+ /**
39
+ * Data query response - 只返回原始数据
40
+ */
41
+ interface DataQueryResponse {
42
+ success: boolean;
43
+ message: string;
44
+ data?: {
45
+ // 语义查询结果
46
+ semanticModel?: string;
47
+ columns: Array<{ name: string; type: string }>;
48
+ rows?: Array<Array<unknown>>;
49
+ rowsObject?: Array<Record<string, unknown>>;
50
+ // SQL 查询结果
51
+ tableName?: string;
52
+ executedSql?: string;
53
+ // 通用元数据
54
+ metadata: {
55
+ rowCount: number;
56
+ executionTimeMs?: number;
57
+ };
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Execute data query (semantic or SQL)
63
+ */
64
+ export async function executeDataQuery(
65
+ request: FastifyRequest,
66
+ reply: FastifyReply
67
+ ): Promise<DataQueryResponse> {
68
+ const tenantId = getTenantId(request);
69
+ const body = request.body as DataQueryRequest;
70
+
71
+ try {
72
+ // Validate request
73
+ if (!body.serverKey && !body.datasourceId) {
74
+ reply.code(400);
75
+ return {
76
+ success: false,
77
+ message: "Either serverKey or datasourceId must be provided",
78
+ };
79
+ }
80
+
81
+ // Determine query type
82
+ const isSemanticQuery = body.metrics && body.metrics.length > 0;
83
+ const isSqlQuery = body.customSql && body.customSql.trim().length > 0;
84
+
85
+ if (!isSemanticQuery && !isSqlQuery) {
86
+ reply.code(400);
87
+ return {
88
+ success: false,
89
+ message: "Either metrics (for semantic query) or customSql (for SQL query) must be provided",
90
+ };
91
+ }
92
+
93
+ if (isSemanticQuery && isSqlQuery) {
94
+ reply.code(400);
95
+ return {
96
+ success: false,
97
+ message: "Cannot provide both metrics and customSql. Use one query type only.",
98
+ };
99
+ }
100
+
101
+ // Get server config
102
+ const storeLattice = getStoreLattice("default", "metrics");
103
+ const store: MetricsServerConfigStore = storeLattice.store;
104
+
105
+ if (!body.serverKey) {
106
+ reply.code(400);
107
+ return {
108
+ success: false,
109
+ message: "serverKey is required",
110
+ };
111
+ }
112
+
113
+ const config = await store.getConfigByKey(tenantId, body.serverKey);
114
+ if (!config) {
115
+ reply.code(404);
116
+ return {
117
+ success: false,
118
+ message: `Metrics server configuration not found: ${body.serverKey}`,
119
+ };
120
+ }
121
+
122
+ if (config.config.type !== "semantic") {
123
+ reply.code(400);
124
+ return {
125
+ success: false,
126
+ message: "This endpoint only supports semantic metrics servers",
127
+ };
128
+ }
129
+
130
+ if (!body.datasourceId) {
131
+ reply.code(400);
132
+ return {
133
+ success: false,
134
+ message: "datasourceId is required",
135
+ };
136
+ }
137
+
138
+ // Check if server is registered
139
+ if (!metricsServerManager.hasServer(tenantId, body.serverKey)) {
140
+ reply.code(400);
141
+ return {
142
+ success: false,
143
+ message: `Metrics server not registered: ${body.serverKey}. Please register the server first.`,
144
+ };
145
+ }
146
+
147
+ // Get client from manager (read-only, no registration)
148
+ const client = metricsServerManager.getClient(tenantId, body.serverKey) as SemanticMetricsClient;
149
+
150
+ // Execute query based on type
151
+ if (isSemanticQuery) {
152
+ return await executeSemanticQuery(client, body, reply);
153
+ } else {
154
+ return await executeSqlQuery(client, body, reply);
155
+ }
156
+ } catch (error) {
157
+ console.error("Failed to execute data query:", error);
158
+ reply.code(500);
159
+ return {
160
+ success: false,
161
+ message: `Failed to execute data query: ${error instanceof Error ? error.message : String(error)}`,
162
+ };
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Execute semantic query
168
+ */
169
+ async function executeSemanticQuery(
170
+ client: SemanticMetricsClient,
171
+ body: DataQueryRequest,
172
+ reply: FastifyReply
173
+ ): Promise<DataQueryResponse> {
174
+ const semanticFilters: SemanticMetricsFilter[] = (body.filters || []).map(f => ({
175
+ dimension: f.dimension,
176
+ operator: f.operator,
177
+ values: f.values,
178
+ }));
179
+
180
+ const result = await client.semanticQuery({
181
+ datasourceId: body.datasourceId!,
182
+ metrics: body.metrics!,
183
+ groupBy: body.groupBy,
184
+ filters: semanticFilters,
185
+ limit: body.limit || 1000,
186
+ });
187
+
188
+ // 直接返回原始数据,不做 ECharts 转换
189
+ return {
190
+ success: true,
191
+ message: "Semantic query executed successfully",
192
+ data: {
193
+ semanticModel: result.semanticModel,
194
+ columns: result.columns,
195
+ rows: result.rows,
196
+ rowsObject: result.rowsObject,
197
+ metadata: {
198
+ rowCount: result.rowCount,
199
+ executionTimeMs: result.executionTimeMs,
200
+ },
201
+ },
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Execute SQL query
207
+ */
208
+ async function executeSqlQuery(
209
+ client: SemanticMetricsClient,
210
+ body: DataQueryRequest,
211
+ reply: FastifyReply
212
+ ): Promise<DataQueryResponse> {
213
+ const result = await client.executeSqlQuery({
214
+ datasourceId: body.datasourceId!,
215
+ customSql: body.customSql!,
216
+ params: body.params,
217
+ limit: body.limit || 100,
218
+ });
219
+
220
+ // 直接返回原始数据,不做 ECharts 转换
221
+ return {
222
+ success: true,
223
+ message: "SQL query executed successfully",
224
+ data: {
225
+ tableName: result.tableName,
226
+ columns: result.columns.map(name => ({ name, type: 'unknown' })),
227
+ rows: result.rows,
228
+ rowsObject: result.rowsObject,
229
+ executedSql: result.executedSql,
230
+ metadata: {
231
+ rowCount: result.rowCount,
232
+ executionTimeMs: result.executionTimeMs,
233
+ },
234
+ },
235
+ };
236
+ }
@@ -220,17 +220,16 @@ interface SemanticQueryResponse {
220
220
  success: boolean;
221
221
  message: string;
222
222
  data?: {
223
- datasourceId: string | number;
224
- metrics: string[];
223
+ semanticModel: string;
224
+ columns: string[];
225
225
  dataPoints: Array<{
226
226
  timestamp?: number;
227
227
  value: number;
228
- metricName?: string;
229
228
  labels?: Record<string, string>;
230
229
  }>;
231
230
  metadata?: {
232
231
  rowCount?: number;
233
- queryTimeMs?: number;
232
+ columnCount?: number;
234
233
  };
235
234
  };
236
235
  }
@@ -857,40 +856,45 @@ export async function querySemanticMetrics(
857
856
  const result = await client.semanticQuery(body);
858
857
 
859
858
  // Transform SemanticMetricsQueryResponse to response format
860
- // The response contains results array with metric data
859
+ // The response contains columns and rows arrays
860
+ const columnNames = result.columns.map(col => col.name);
861
861
  const allDataPoints: Array<{
862
862
  timestamp?: number;
863
863
  value: number;
864
- metricName?: string;
865
864
  labels?: Record<string, string>;
866
865
  }> = [];
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
- }
866
+
867
+ // Find timestamp column and value column indices
868
+ const timestampColIdx = columnNames.findIndex(col =>
869
+ col.toLowerCase().includes('date') || col.toLowerCase().includes('time')
870
+ );
871
+ const valueColIdx = columnNames.findIndex(col =>
872
+ col.toLowerCase().includes('value') || col.toLowerCase().includes('rate') || col.toLowerCase().includes('amt')
873
+ );
874
+
875
+ for (const row of result.rows) {
876
+ const timestamp = timestampColIdx >= 0 ? row[timestampColIdx] : undefined;
877
+ const value = valueColIdx >= 0 ? row[valueColIdx] : row[row.length - 1];
878
+
879
+ allDataPoints.push({
880
+ timestamp: timestamp ? new Date(String(timestamp)).getTime() : undefined,
881
+ value: typeof value === 'number' ? value : Number(value) || 0,
882
+ labels: Object.fromEntries(
883
+ columnNames.map((col, idx) => [col, String(row[idx] ?? '')])
884
+ ),
885
+ });
882
886
  }
883
887
 
884
888
  return {
885
889
  success: true,
886
890
  message: "Semantic query executed successfully",
887
891
  data: {
888
- datasourceId: result.datasourceId,
889
- metrics: metricNames,
892
+ semanticModel: result.semanticModel,
893
+ columns: columnNames,
890
894
  dataPoints: allDataPoints,
891
895
  metadata: {
892
- rowCount: allDataPoints.length,
893
- queryTimeMs: result.totalExecutionTimeMs,
896
+ rowCount: result.rows.length,
897
+ columnCount: result.columns.length,
894
898
  },
895
899
  },
896
900
  };
@@ -457,6 +457,46 @@ export class WorkspaceController {
457
457
  const webStream = body.stream();
458
458
  const nodeStream = Readable.fromWeb(webStream);
459
459
  const contentType = body.contentType ?? inferredContentType;
460
+ console.log(`[viewFile] Sandbox returned stream, contentType: ${contentType}, filename: ${filename}`);
461
+
462
+ // Check if it's HTML and needs context injection
463
+ const isHtml = contentType?.toLowerCase().includes("text/html") ||
464
+ filename.toLowerCase().endsWith(".html") ||
465
+ filename.toLowerCase().endsWith(".htm");
466
+
467
+ if (isHtml) {
468
+ console.log(`[viewFile] HTML stream detected, collecting for context injection`);
469
+ // Collect stream content
470
+ const chunks: Buffer[] = [];
471
+ for await (const chunk of nodeStream) {
472
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
473
+ }
474
+ let content = Buffer.concat(chunks).toString("utf-8");
475
+ const contextScript = `<script>window.__AI2APP_CONTEXT__=${JSON.stringify({
476
+ tenantId,
477
+ workspaceId,
478
+ projectId,
479
+ timestamp: Date.now()
480
+ })};</script>`;
481
+
482
+ if (content.toLowerCase().includes("</head>")) {
483
+ content = content.replace(/<\/head>/i, `${contextScript}</head>`);
484
+ console.log(`[viewFile] Context script injected before </head> (stream)`);
485
+ } else if (content.toLowerCase().includes("<html>")) {
486
+ content = content.replace(/<html>/i, `<html>${contextScript}`);
487
+ console.log(`[viewFile] Context script injected after <html> (stream)`);
488
+ } else {
489
+ content = contextScript + content;
490
+ console.log(`[viewFile] Context script prepended to content (stream)`);
491
+ }
492
+
493
+ return reply
494
+ .status(200)
495
+ .type(contentType)
496
+ .header("Content-Disposition", "inline")
497
+ .send(Buffer.from(content, "utf-8"));
498
+ }
499
+
460
500
  // inline for viewing, not attachment for download
461
501
  return reply
462
502
  .status(200)
@@ -487,6 +527,33 @@ export class WorkspaceController {
487
527
  return reply.status(502).send({ success: false, error: "Unexpected view response format" });
488
528
  }
489
529
 
530
+ // Inject AI2APP context script for HTML files (sandbox storage)
531
+ const isHtml = contentType?.toLowerCase().includes("text/html") ||
532
+ filename.toLowerCase().endsWith(".html") ||
533
+ filename.toLowerCase().endsWith(".htm");
534
+ if (isHtml) {
535
+ console.log(`[viewFile] Injecting AI2APP context for sandbox HTML file: ${filename}, tenantId: ${tenantId}, contentType: ${contentType}`);
536
+ let content = buf.toString("utf-8");
537
+ const contextScript = `<script>window.__AI2APP_CONTEXT__=${JSON.stringify({
538
+ tenantId,
539
+ workspaceId,
540
+ projectId,
541
+ timestamp: Date.now()
542
+ })};</script>`;
543
+
544
+ if (content.toLowerCase().includes("</head>")) {
545
+ content = content.replace(/<\/head>/i, `${contextScript}</head>`);
546
+ console.log(`[viewFile] Context script injected before </head>`);
547
+ } else if (content.toLowerCase().includes("<html>")) {
548
+ content = content.replace(/<html>/i, `<html>${contextScript}`);
549
+ console.log(`[viewFile] Context script injected after <html>`);
550
+ } else {
551
+ content = contextScript + content;
552
+ console.log(`[viewFile] Context script prepended to content`);
553
+ }
554
+ buf = Buffer.from(content, "utf-8");
555
+ }
556
+
490
557
  return reply
491
558
  .status(200)
492
559
  .type(contentType)
@@ -498,7 +565,34 @@ export class WorkspaceController {
498
565
  const content = await backend.read(resolvedPath, 0, Infinity);
499
566
  const filename = this.getFilenameFromPath(resolvedPath);
500
567
  const mimeType = this.getMimeType(filename);
501
- const buffer = Buffer.from(content, "utf-8");
568
+
569
+ // Inject AI2APP context script for HTML files
570
+ let finalContent = content;
571
+ const isHtmlFs = mimeType?.toLowerCase().includes("text/html") ||
572
+ filename.toLowerCase().endsWith(".html") ||
573
+ filename.toLowerCase().endsWith(".htm");
574
+ if (isHtmlFs) {
575
+ console.log(`[viewFile] Injecting AI2APP context for filesystem HTML file: ${filename}, tenantId: ${tenantId}, mimeType: ${mimeType}`);
576
+ const contextScript = `<script>window.__AI2APP_CONTEXT__=${JSON.stringify({
577
+ tenantId,
578
+ workspaceId,
579
+ projectId,
580
+ timestamp: Date.now()
581
+ })};</script>`;
582
+ // Insert before </head> or </html>
583
+ if (content.toLowerCase().includes("</head>")) {
584
+ finalContent = content.replace(/<\/head>/i, `${contextScript}</head>`);
585
+ console.log(`[viewFile] Context script injected before </head>`);
586
+ } else if (content.toLowerCase().includes("</html>")) {
587
+ finalContent = content.replace(/<\/html>/i, `${contextScript}</html>`);
588
+ console.log(`[viewFile] Context script injected before </html>`);
589
+ } else {
590
+ finalContent = content + contextScript;
591
+ console.log(`[viewFile] Context script appended to content`);
592
+ }
593
+ }
594
+
595
+ const buffer = Buffer.from(finalContent, "utf-8");
502
596
 
503
597
  return reply
504
598
  .status(200)
package/src/index.ts CHANGED
@@ -3,6 +3,8 @@ import cors from "@fastify/cors";
3
3
  import multipart from "@fastify/multipart";
4
4
  import sensible from "@fastify/sensible";
5
5
  import websocket from "@fastify/websocket";
6
+ import staticPlugin from "@fastify/static";
7
+ import path from "path";
6
8
  import { registerLatticeRoutes } from "./routes";
7
9
  import { configureSwagger } from "./swagger";
8
10
  import {
@@ -122,6 +124,12 @@ app.register(multipart, {
122
124
  });
123
125
  app.register(websocket);
124
126
 
127
+ // Register static file serving for SDK
128
+ app.register(staticPlugin, {
129
+ root: path.join(__dirname, "../public"),
130
+ prefix: "/",
131
+ });
132
+
125
133
  // Error handler
126
134
  app.setErrorHandler((error, request, reply) => {
127
135
  // Convert headers to strings (Fastify headers can be string | string[])
@@ -197,6 +205,9 @@ const start = async (config?: LatticeGatewayConfig) => {
197
205
  // Access via: request.server.loggerLattice or app.loggerLattice
198
206
  app.decorate("loggerLattice", loggerLattice);
199
207
 
208
+ // Register all routes
209
+ registerLatticeRoutes(app);
210
+
200
211
  // Register sandbox manager if not already registered
201
212
  if (!sandboxLatticeManager.hasLattice("default")) {
202
213
  const sandboxBaseURL = process.env.SANDBOX_BASE_URL || "http://localhost:8080";
@@ -12,6 +12,7 @@ import * as modelsController from "../controllers/models";
12
12
  import * as healthController from "../controllers/health";
13
13
  import * as skillsController from "../controllers/skills";
14
14
  import * as toolsController from "../controllers/tools";
15
+ import * as dataQueryController from "../controllers/data-query";
15
16
  import {
16
17
  createRunSchema,
17
18
  getAllMemoryItemsSchema,
@@ -27,6 +28,7 @@ import {
27
28
  getConfigSchema,
28
29
  getHealthSchema,
29
30
  getSandboxUrlSchema,
31
+ dataQuerySchema,
30
32
  } from "../schemas";
31
33
  import { registerSandboxProxyRoutes } from "../controllers/sandbox";
32
34
  import { registerWorkspaceRoutes } from "../controllers/workspace";
@@ -307,6 +309,15 @@ export const registerLatticeRoutes = (app: FastifyInstance): void => {
307
309
 
308
310
  registerMetricsServerConfigRoutes(app);
309
311
 
312
+ // Data query route
313
+ app.post<{
314
+ Body: any;
315
+ }>(
316
+ "/api/data/query",
317
+ { schema: dataQuerySchema },
318
+ dataQueryController.executeDataQuery
319
+ );
320
+
310
321
  registerMcpServerConfigRoutes(app);
311
322
 
312
323
  registerUserRoutes(app);
@@ -0,0 +1,69 @@
1
+ import { FastifySchema } from "fastify";
2
+
3
+ export const dataQuerySchema: FastifySchema = {
4
+ description: "Execute data query (semantic or SQL)",
5
+ tags: ["Data Query"],
6
+ summary: "Query Data",
7
+ body: {
8
+ type: "object",
9
+ properties: {
10
+ serverKey: {
11
+ type: "string",
12
+ description: "Target semantic metrics server key (optional if configured in runConfig)"
13
+ },
14
+ datasourceId: {
15
+ type: "string",
16
+ description: "Data source ID (optional if configured in runConfig)"
17
+ },
18
+ // Semantic query parameters
19
+ metrics: {
20
+ type: "array",
21
+ items: { type: "string" },
22
+ description: "Array of metric names for semantic query"
23
+ },
24
+ groupBy: {
25
+ type: "array",
26
+ items: { type: "string" },
27
+ description: "Optional array of dimensions to group by"
28
+ },
29
+ filters: {
30
+ type: "array",
31
+ items: {
32
+ type: "object",
33
+ properties: {
34
+ dimension: { type: "string" },
35
+ operator: { type: "string" },
36
+ values: {
37
+ type: "array",
38
+ items: { type: ["string", "number", "boolean"] }
39
+ }
40
+ },
41
+ required: ["dimension", "operator", "values"]
42
+ },
43
+ description: "Optional array of filters"
44
+ },
45
+ // SQL query parameters
46
+ customSql: {
47
+ type: "string",
48
+ description: "Custom SQL query string with named parameters"
49
+ },
50
+ params: {
51
+ type: "object",
52
+ additionalProperties: { type: ["string", "number", "boolean"] },
53
+ description: "Optional parameters for SQL query"
54
+ },
55
+ // Common parameters
56
+ limit: {
57
+ type: "number",
58
+ description: "Maximum number of results (default: 1000)"
59
+ },
60
+ format: {
61
+ type: "string",
62
+ enum: ["echarts", "raw"],
63
+ description: "Response format (default: echarts)"
64
+ }
65
+ }
66
+ },
67
+ // Response schema temporarily removed - format not yet finalized
68
+ // TODO: Add proper response schema once data format is confirmed
69
+ };
@@ -847,3 +847,6 @@ export const getSandboxUrlSchema: FastifySchema = {
847
847
  },
848
848
  },
849
849
  };
850
+
851
+ // Data Query Schema
852
+ export { dataQuerySchema } from "./data-query";
@@ -55,6 +55,8 @@ const handleAgentTask = async (
55
55
  headers: {
56
56
  "Content-Type": "application/json",
57
57
  "x-tenant-id": tenant_id,
58
+ "x-workspace-id": runConfig?.workspaceId as string,
59
+ "x-project-id": runConfig?.projectId as string,
58
60
  },
59
61
  }).catch((err) => {
60
62
  console.error(`fetch请求失败: ${err.message || String(err)}`);