@robosystems/client 0.2.11 → 0.2.13

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,218 @@
1
+ 'use client';
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.QueuedAgentError = exports.AgentClient = void 0;
5
+ /**
6
+ * Enhanced Agent Client with SSE support
7
+ * Provides intelligent agent execution with automatic strategy selection
8
+ */
9
+ const sdk_gen_1 = require("../sdk/sdk.gen");
10
+ const SSEClient_1 = require("./SSEClient");
11
+ class AgentClient {
12
+ constructor(config) {
13
+ this.config = config;
14
+ }
15
+ /**
16
+ * Execute agent query with automatic agent selection
17
+ */
18
+ async executeQuery(graphId, request, options = {}) {
19
+ const data = {
20
+ url: '/v1/graphs/{graph_id}/agent',
21
+ path: { graph_id: graphId },
22
+ body: {
23
+ message: request.message,
24
+ history: request.history,
25
+ context: request.context,
26
+ mode: request.mode,
27
+ enable_rag: request.enableRag,
28
+ force_extended_analysis: request.forceExtendedAnalysis,
29
+ },
30
+ };
31
+ const response = await (0, sdk_gen_1.autoSelectAgent)(data);
32
+ const responseData = response.data;
33
+ // Check if this is an immediate response (sync execution)
34
+ if (responseData?.content !== undefined && responseData?.agent_used) {
35
+ return {
36
+ content: responseData.content,
37
+ agent_used: responseData.agent_used,
38
+ mode_used: responseData.mode_used,
39
+ metadata: responseData.metadata,
40
+ tokens_used: responseData.tokens_used,
41
+ confidence_score: responseData.confidence_score,
42
+ execution_time: responseData.execution_time,
43
+ timestamp: new Date().toISOString(),
44
+ };
45
+ }
46
+ // Check if this is a queued response (async Celery execution)
47
+ if (responseData?.operation_id) {
48
+ const queuedResponse = responseData;
49
+ // If user doesn't want to wait, throw with queue info
50
+ if (options.maxWait === 0) {
51
+ throw new QueuedAgentError(queuedResponse);
52
+ }
53
+ // Use SSE to monitor the operation
54
+ return this.waitForAgentCompletion(queuedResponse.operation_id, options);
55
+ }
56
+ // Unexpected response format
57
+ throw new Error('Unexpected response format from agent endpoint');
58
+ }
59
+ /**
60
+ * Execute specific agent type
61
+ */
62
+ async executeAgent(graphId, agentType, request, options = {}) {
63
+ const data = {
64
+ url: '/v1/graphs/{graph_id}/agent/{agent_type}',
65
+ path: { graph_id: graphId, agent_type: agentType },
66
+ body: {
67
+ message: request.message,
68
+ history: request.history,
69
+ context: request.context,
70
+ mode: request.mode,
71
+ enable_rag: request.enableRag,
72
+ force_extended_analysis: request.forceExtendedAnalysis,
73
+ },
74
+ };
75
+ const response = await (0, sdk_gen_1.executeSpecificAgent)(data);
76
+ const responseData = response.data;
77
+ // Check if this is an immediate response (sync execution)
78
+ if (responseData?.content !== undefined && responseData?.agent_used) {
79
+ return {
80
+ content: responseData.content,
81
+ agent_used: responseData.agent_used,
82
+ mode_used: responseData.mode_used,
83
+ metadata: responseData.metadata,
84
+ tokens_used: responseData.tokens_used,
85
+ confidence_score: responseData.confidence_score,
86
+ execution_time: responseData.execution_time,
87
+ timestamp: new Date().toISOString(),
88
+ };
89
+ }
90
+ // Check if this is a queued response (async Celery execution)
91
+ if (responseData?.operation_id) {
92
+ const queuedResponse = responseData;
93
+ // If user doesn't want to wait, throw with queue info
94
+ if (options.maxWait === 0) {
95
+ throw new QueuedAgentError(queuedResponse);
96
+ }
97
+ // Use SSE to monitor the operation
98
+ return this.waitForAgentCompletion(queuedResponse.operation_id, options);
99
+ }
100
+ // Unexpected response format
101
+ throw new Error('Unexpected response format from agent endpoint');
102
+ }
103
+ async waitForAgentCompletion(operationId, options) {
104
+ return new Promise((resolve, reject) => {
105
+ const sseClient = new SSEClient_1.SSEClient(this.config);
106
+ sseClient
107
+ .connect(operationId)
108
+ .then(() => {
109
+ let result = null;
110
+ // Listen for progress events
111
+ sseClient.on(SSEClient_1.EventType.OPERATION_PROGRESS, (data) => {
112
+ options.onProgress?.(data.message, data.progress_percentage);
113
+ });
114
+ // Listen for agent-specific events
115
+ sseClient.on('agent_started', (data) => {
116
+ options.onProgress?.(`Agent ${data.agent_type} started`, 0);
117
+ });
118
+ sseClient.on('agent_initialized', (data) => {
119
+ options.onProgress?.(`${data.agent_name} initialized`, 10);
120
+ });
121
+ sseClient.on('progress', (data) => {
122
+ options.onProgress?.(data.message, data.percentage);
123
+ });
124
+ sseClient.on('agent_completed', (data) => {
125
+ result = {
126
+ content: data.content,
127
+ agent_used: data.agent_used,
128
+ mode_used: data.mode_used,
129
+ metadata: data.metadata,
130
+ tokens_used: data.tokens_used,
131
+ confidence_score: data.confidence_score,
132
+ execution_time: data.execution_time,
133
+ timestamp: data.timestamp || new Date().toISOString(),
134
+ };
135
+ sseClient.close();
136
+ resolve(result);
137
+ });
138
+ // Fallback to generic completion event
139
+ sseClient.on(SSEClient_1.EventType.OPERATION_COMPLETED, (data) => {
140
+ if (!result) {
141
+ const agentResult = data.result || data;
142
+ result = {
143
+ content: agentResult.content || '',
144
+ agent_used: agentResult.agent_used || 'unknown',
145
+ mode_used: agentResult.mode_used || 'standard',
146
+ metadata: agentResult.metadata,
147
+ tokens_used: agentResult.tokens_used,
148
+ confidence_score: agentResult.confidence_score,
149
+ execution_time: agentResult.execution_time,
150
+ timestamp: agentResult.timestamp || new Date().toISOString(),
151
+ };
152
+ sseClient.close();
153
+ resolve(result);
154
+ }
155
+ });
156
+ sseClient.on(SSEClient_1.EventType.OPERATION_ERROR, (error) => {
157
+ sseClient.close();
158
+ reject(new Error(error.message || error.error));
159
+ });
160
+ sseClient.on(SSEClient_1.EventType.OPERATION_CANCELLED, () => {
161
+ sseClient.close();
162
+ reject(new Error('Agent execution cancelled'));
163
+ });
164
+ // Handle generic error event
165
+ sseClient.on('error', (error) => {
166
+ sseClient.close();
167
+ reject(new Error(error.error || error.message || 'Agent execution failed'));
168
+ });
169
+ })
170
+ .catch(reject);
171
+ });
172
+ }
173
+ /**
174
+ * Convenience method for simple agent queries with auto-selection
175
+ */
176
+ async query(graphId, message, context) {
177
+ return this.executeQuery(graphId, { message, context }, { mode: 'auto' });
178
+ }
179
+ /**
180
+ * Execute financial agent for financial analysis
181
+ */
182
+ async analyzeFinancials(graphId, message, options = {}) {
183
+ return this.executeAgent(graphId, 'financial', { message }, options);
184
+ }
185
+ /**
186
+ * Execute research agent for deep research
187
+ */
188
+ async research(graphId, message, options = {}) {
189
+ return this.executeAgent(graphId, 'research', { message }, options);
190
+ }
191
+ /**
192
+ * Execute RAG agent for fast retrieval
193
+ */
194
+ async rag(graphId, message, options = {}) {
195
+ return this.executeAgent(graphId, 'rag', { message }, options);
196
+ }
197
+ /**
198
+ * Cancel any active SSE connections
199
+ */
200
+ close() {
201
+ if (this.sseClient) {
202
+ this.sseClient.close();
203
+ this.sseClient = undefined;
204
+ }
205
+ }
206
+ }
207
+ exports.AgentClient = AgentClient;
208
+ /**
209
+ * Error thrown when agent execution is queued and maxWait is 0
210
+ */
211
+ class QueuedAgentError extends Error {
212
+ constructor(queueInfo) {
213
+ super('Agent execution was queued');
214
+ this.queueInfo = queueInfo;
215
+ this.name = 'QueuedAgentError';
216
+ }
217
+ }
218
+ exports.QueuedAgentError = QueuedAgentError;
@@ -0,0 +1,321 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Enhanced Agent Client with SSE support
5
+ * Provides intelligent agent execution with automatic strategy selection
6
+ */
7
+
8
+ import { autoSelectAgent, executeSpecificAgent } from '../sdk/sdk.gen'
9
+ import type { AutoSelectAgentData, ExecuteSpecificAgentData } from '../sdk/types.gen'
10
+ import { EventType, SSEClient } from './SSEClient'
11
+
12
+ export interface AgentQueryRequest {
13
+ message: string
14
+ history?: Array<{ role: string; content: string }>
15
+ context?: Record<string, any>
16
+ mode?: 'quick' | 'standard' | 'extended' | 'streaming'
17
+ enableRag?: boolean
18
+ forceExtendedAnalysis?: boolean
19
+ }
20
+
21
+ export interface AgentOptions {
22
+ mode?: 'auto' | 'sync' | 'async'
23
+ maxWait?: number
24
+ onProgress?: (message: string, percentage?: number) => void
25
+ }
26
+
27
+ export interface AgentResult {
28
+ content: string
29
+ agent_used: string
30
+ mode_used: 'quick' | 'standard' | 'extended' | 'streaming'
31
+ metadata?: Record<string, any>
32
+ tokens_used?: {
33
+ prompt_tokens: number
34
+ completion_tokens: number
35
+ total_tokens: number
36
+ }
37
+ confidence_score?: number
38
+ execution_time?: number
39
+ timestamp?: string
40
+ }
41
+
42
+ export interface QueuedAgentResponse {
43
+ status: 'queued'
44
+ operation_id: string
45
+ message: string
46
+ sse_endpoint?: string
47
+ }
48
+
49
+ export class AgentClient {
50
+ private sseClient?: SSEClient
51
+ private config: {
52
+ baseUrl: string
53
+ credentials?: 'include' | 'same-origin' | 'omit'
54
+ headers?: Record<string, string>
55
+ token?: string
56
+ }
57
+
58
+ constructor(config: {
59
+ baseUrl: string
60
+ credentials?: 'include' | 'same-origin' | 'omit'
61
+ headers?: Record<string, string>
62
+ token?: string
63
+ }) {
64
+ this.config = config
65
+ }
66
+
67
+ /**
68
+ * Execute agent query with automatic agent selection
69
+ */
70
+ async executeQuery(
71
+ graphId: string,
72
+ request: AgentQueryRequest,
73
+ options: AgentOptions = {}
74
+ ): Promise<AgentResult> {
75
+ const data: AutoSelectAgentData = {
76
+ url: '/v1/graphs/{graph_id}/agent' as const,
77
+ path: { graph_id: graphId },
78
+ body: {
79
+ message: request.message,
80
+ history: request.history,
81
+ context: request.context,
82
+ mode: request.mode,
83
+ enable_rag: request.enableRag,
84
+ force_extended_analysis: request.forceExtendedAnalysis,
85
+ },
86
+ }
87
+
88
+ const response = await autoSelectAgent(data)
89
+ const responseData = response.data as any
90
+
91
+ // Check if this is an immediate response (sync execution)
92
+ if (responseData?.content !== undefined && responseData?.agent_used) {
93
+ return {
94
+ content: responseData.content,
95
+ agent_used: responseData.agent_used,
96
+ mode_used: responseData.mode_used,
97
+ metadata: responseData.metadata,
98
+ tokens_used: responseData.tokens_used,
99
+ confidence_score: responseData.confidence_score,
100
+ execution_time: responseData.execution_time,
101
+ timestamp: new Date().toISOString(),
102
+ }
103
+ }
104
+
105
+ // Check if this is a queued response (async Celery execution)
106
+ if (responseData?.operation_id) {
107
+ const queuedResponse = responseData as QueuedAgentResponse
108
+
109
+ // If user doesn't want to wait, throw with queue info
110
+ if (options.maxWait === 0) {
111
+ throw new QueuedAgentError(queuedResponse)
112
+ }
113
+
114
+ // Use SSE to monitor the operation
115
+ return this.waitForAgentCompletion(queuedResponse.operation_id, options)
116
+ }
117
+
118
+ // Unexpected response format
119
+ throw new Error('Unexpected response format from agent endpoint')
120
+ }
121
+
122
+ /**
123
+ * Execute specific agent type
124
+ */
125
+ async executeAgent(
126
+ graphId: string,
127
+ agentType: string,
128
+ request: AgentQueryRequest,
129
+ options: AgentOptions = {}
130
+ ): Promise<AgentResult> {
131
+ const data: ExecuteSpecificAgentData = {
132
+ url: '/v1/graphs/{graph_id}/agent/{agent_type}' as const,
133
+ path: { graph_id: graphId, agent_type: agentType },
134
+ body: {
135
+ message: request.message,
136
+ history: request.history,
137
+ context: request.context,
138
+ mode: request.mode,
139
+ enable_rag: request.enableRag,
140
+ force_extended_analysis: request.forceExtendedAnalysis,
141
+ },
142
+ }
143
+
144
+ const response = await executeSpecificAgent(data)
145
+ const responseData = response.data as any
146
+
147
+ // Check if this is an immediate response (sync execution)
148
+ if (responseData?.content !== undefined && responseData?.agent_used) {
149
+ return {
150
+ content: responseData.content,
151
+ agent_used: responseData.agent_used,
152
+ mode_used: responseData.mode_used,
153
+ metadata: responseData.metadata,
154
+ tokens_used: responseData.tokens_used,
155
+ confidence_score: responseData.confidence_score,
156
+ execution_time: responseData.execution_time,
157
+ timestamp: new Date().toISOString(),
158
+ }
159
+ }
160
+
161
+ // Check if this is a queued response (async Celery execution)
162
+ if (responseData?.operation_id) {
163
+ const queuedResponse = responseData as QueuedAgentResponse
164
+
165
+ // If user doesn't want to wait, throw with queue info
166
+ if (options.maxWait === 0) {
167
+ throw new QueuedAgentError(queuedResponse)
168
+ }
169
+
170
+ // Use SSE to monitor the operation
171
+ return this.waitForAgentCompletion(queuedResponse.operation_id, options)
172
+ }
173
+
174
+ // Unexpected response format
175
+ throw new Error('Unexpected response format from agent endpoint')
176
+ }
177
+
178
+ private async waitForAgentCompletion(
179
+ operationId: string,
180
+ options: AgentOptions
181
+ ): Promise<AgentResult> {
182
+ return new Promise((resolve, reject) => {
183
+ const sseClient = new SSEClient(this.config)
184
+
185
+ sseClient
186
+ .connect(operationId)
187
+ .then(() => {
188
+ let result: AgentResult | null = null
189
+
190
+ // Listen for progress events
191
+ sseClient.on(EventType.OPERATION_PROGRESS, (data) => {
192
+ options.onProgress?.(data.message, data.progress_percentage)
193
+ })
194
+
195
+ // Listen for agent-specific events
196
+ sseClient.on('agent_started' as EventType, (data) => {
197
+ options.onProgress?.(`Agent ${data.agent_type} started`, 0)
198
+ })
199
+
200
+ sseClient.on('agent_initialized' as EventType, (data) => {
201
+ options.onProgress?.(`${data.agent_name} initialized`, 10)
202
+ })
203
+
204
+ sseClient.on('progress' as EventType, (data) => {
205
+ options.onProgress?.(data.message, data.percentage)
206
+ })
207
+
208
+ sseClient.on('agent_completed' as EventType, (data) => {
209
+ result = {
210
+ content: data.content,
211
+ agent_used: data.agent_used,
212
+ mode_used: data.mode_used,
213
+ metadata: data.metadata,
214
+ tokens_used: data.tokens_used,
215
+ confidence_score: data.confidence_score,
216
+ execution_time: data.execution_time,
217
+ timestamp: data.timestamp || new Date().toISOString(),
218
+ }
219
+ sseClient.close()
220
+ resolve(result)
221
+ })
222
+
223
+ // Fallback to generic completion event
224
+ sseClient.on(EventType.OPERATION_COMPLETED, (data) => {
225
+ if (!result) {
226
+ const agentResult = data.result || data
227
+ result = {
228
+ content: agentResult.content || '',
229
+ agent_used: agentResult.agent_used || 'unknown',
230
+ mode_used: agentResult.mode_used || 'standard',
231
+ metadata: agentResult.metadata,
232
+ tokens_used: agentResult.tokens_used,
233
+ confidence_score: agentResult.confidence_score,
234
+ execution_time: agentResult.execution_time,
235
+ timestamp: agentResult.timestamp || new Date().toISOString(),
236
+ }
237
+ sseClient.close()
238
+ resolve(result)
239
+ }
240
+ })
241
+
242
+ sseClient.on(EventType.OPERATION_ERROR, (error) => {
243
+ sseClient.close()
244
+ reject(new Error(error.message || error.error))
245
+ })
246
+
247
+ sseClient.on(EventType.OPERATION_CANCELLED, () => {
248
+ sseClient.close()
249
+ reject(new Error('Agent execution cancelled'))
250
+ })
251
+
252
+ // Handle generic error event
253
+ sseClient.on('error' as EventType, (error) => {
254
+ sseClient.close()
255
+ reject(new Error(error.error || error.message || 'Agent execution failed'))
256
+ })
257
+ })
258
+ .catch(reject)
259
+ })
260
+ }
261
+
262
+ /**
263
+ * Convenience method for simple agent queries with auto-selection
264
+ */
265
+ async query(
266
+ graphId: string,
267
+ message: string,
268
+ context?: Record<string, any>
269
+ ): Promise<AgentResult> {
270
+ return this.executeQuery(graphId, { message, context }, { mode: 'auto' })
271
+ }
272
+
273
+ /**
274
+ * Execute financial agent for financial analysis
275
+ */
276
+ async analyzeFinancials(
277
+ graphId: string,
278
+ message: string,
279
+ options: AgentOptions = {}
280
+ ): Promise<AgentResult> {
281
+ return this.executeAgent(graphId, 'financial', { message }, options)
282
+ }
283
+
284
+ /**
285
+ * Execute research agent for deep research
286
+ */
287
+ async research(
288
+ graphId: string,
289
+ message: string,
290
+ options: AgentOptions = {}
291
+ ): Promise<AgentResult> {
292
+ return this.executeAgent(graphId, 'research', { message }, options)
293
+ }
294
+
295
+ /**
296
+ * Execute RAG agent for fast retrieval
297
+ */
298
+ async rag(graphId: string, message: string, options: AgentOptions = {}): Promise<AgentResult> {
299
+ return this.executeAgent(graphId, 'rag', { message }, options)
300
+ }
301
+
302
+ /**
303
+ * Cancel any active SSE connections
304
+ */
305
+ close(): void {
306
+ if (this.sseClient) {
307
+ this.sseClient.close()
308
+ this.sseClient = undefined
309
+ }
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Error thrown when agent execution is queued and maxWait is 0
315
+ */
316
+ export class QueuedAgentError extends Error {
317
+ constructor(public queueInfo: QueuedAgentResponse) {
318
+ super('Agent execution was queued')
319
+ this.name = 'QueuedAgentError'
320
+ }
321
+ }
@@ -24,17 +24,30 @@ class QueryClient {
24
24
  mode: options.mode,
25
25
  test_mode: options.testMode,
26
26
  },
27
+ // For streaming mode, don't parse response - get raw Response object
28
+ ...(options.mode === 'stream' ? { parseAs: 'stream' } : {}),
27
29
  };
28
- // Execute the query
29
30
  const response = await (0, sdk_gen_1.executeCypherQuery)(data);
30
- // Check if this is an NDJSON streaming response
31
- // The SDK returns the response object which includes the raw Response
32
- if (response.response) {
31
+ // Check if this is a raw stream response (when parseAs: 'stream')
32
+ if (options.mode === 'stream' && response.response) {
33
33
  const contentType = response.response.headers.get('content-type') || '';
34
- const streamFormat = response.response.headers.get('x-stream-format');
35
- if (contentType.includes('application/x-ndjson') || streamFormat === 'ndjson') {
34
+ if (contentType.includes('application/x-ndjson')) {
36
35
  return this.parseNDJSONResponse(response.response, graphId);
37
36
  }
37
+ else if (contentType.includes('application/json')) {
38
+ // Fallback: parse JSON manually
39
+ const data = await response.response.json();
40
+ if (data.data !== undefined && data.columns) {
41
+ return {
42
+ data: data.data,
43
+ columns: data.columns,
44
+ row_count: data.row_count || data.data.length,
45
+ execution_time_ms: data.execution_time_ms || 0,
46
+ graph_id: graphId,
47
+ timestamp: data.timestamp || new Date().toISOString(),
48
+ };
49
+ }
50
+ }
38
51
  }
39
52
  const responseData = response.data;
40
53
  // Check if this is an immediate response
@@ -58,12 +71,7 @@ class QueryClient {
58
71
  throw new QueuedQueryError(queuedResponse);
59
72
  }
60
73
  // Use SSE to monitor the operation
61
- if (options.mode === 'stream') {
62
- return this.streamQueryResults(queuedResponse.operation_id, options);
63
- }
64
- else {
65
- return this.waitForQueryCompletion(queuedResponse.operation_id, options);
66
- }
74
+ return this.waitForQueryCompletion(queuedResponse.operation_id, options);
67
75
  }
68
76
  // Unexpected response format
69
77
  throw new Error('Unexpected response format from query endpoint');
@@ -73,35 +81,75 @@ class QueryClient {
73
81
  let columns = null;
74
82
  let totalRows = 0;
75
83
  let executionTimeMs = 0;
76
- // Parse NDJSON line by line
77
- const text = await response.text();
78
- const lines = text.trim().split('\n');
79
- for (const line of lines) {
80
- if (!line.trim())
81
- continue;
82
- try {
83
- const chunk = JSON.parse(line);
84
- // Extract columns from first chunk
85
- if (columns === null && chunk.columns) {
86
- columns = chunk.columns;
87
- }
88
- // Aggregate data rows (NDJSON uses "rows", regular JSON uses "data")
89
- if (chunk.rows) {
90
- allData.push(...chunk.rows);
91
- totalRows += chunk.rows.length;
84
+ // Use streaming reader to avoid "body already read" error
85
+ const reader = response.body?.getReader();
86
+ if (!reader) {
87
+ throw new Error('Response body is not readable');
88
+ }
89
+ const decoder = new TextDecoder();
90
+ let buffer = '';
91
+ try {
92
+ while (true) {
93
+ const { done, value } = await reader.read();
94
+ if (done)
95
+ break;
96
+ buffer += decoder.decode(value, { stream: true });
97
+ const lines = buffer.split('\n');
98
+ buffer = lines.pop() || '';
99
+ for (const line of lines) {
100
+ if (!line.trim())
101
+ continue;
102
+ try {
103
+ const chunk = JSON.parse(line);
104
+ // Extract columns from first chunk
105
+ if (columns === null && chunk.columns) {
106
+ columns = chunk.columns;
107
+ }
108
+ // Aggregate data rows (NDJSON uses "rows", regular JSON uses "data")
109
+ if (chunk.rows) {
110
+ allData.push(...chunk.rows);
111
+ totalRows += chunk.rows.length;
112
+ }
113
+ else if (chunk.data) {
114
+ allData.push(...chunk.data);
115
+ totalRows += chunk.data.length;
116
+ }
117
+ // Track execution time (use max from all chunks)
118
+ if (chunk.execution_time_ms) {
119
+ executionTimeMs = Math.max(executionTimeMs, chunk.execution_time_ms);
120
+ }
121
+ }
122
+ catch (error) {
123
+ console.error('Failed to parse NDJSON line:', error);
124
+ }
92
125
  }
93
- else if (chunk.data) {
94
- allData.push(...chunk.data);
95
- totalRows += chunk.data.length;
126
+ }
127
+ // Parse any remaining buffer
128
+ if (buffer.trim()) {
129
+ try {
130
+ const chunk = JSON.parse(buffer);
131
+ if (columns === null && chunk.columns) {
132
+ columns = chunk.columns;
133
+ }
134
+ if (chunk.rows) {
135
+ allData.push(...chunk.rows);
136
+ totalRows += chunk.rows.length;
137
+ }
138
+ else if (chunk.data) {
139
+ allData.push(...chunk.data);
140
+ totalRows += chunk.data.length;
141
+ }
142
+ if (chunk.execution_time_ms) {
143
+ executionTimeMs = Math.max(executionTimeMs, chunk.execution_time_ms);
144
+ }
96
145
  }
97
- // Track execution time (use max from all chunks)
98
- if (chunk.execution_time_ms) {
99
- executionTimeMs = Math.max(executionTimeMs, chunk.execution_time_ms);
146
+ catch (error) {
147
+ console.error('Failed to parse final NDJSON line:', error);
100
148
  }
101
149
  }
102
- catch (error) {
103
- throw new Error(`Failed to parse NDJSON line: ${error}`);
104
- }
150
+ }
151
+ catch (error) {
152
+ throw new Error(`NDJSON stream reading error: ${error}`);
105
153
  }
106
154
  // Return aggregated result
107
155
  return {