@robosystems/client 0.2.11 → 0.2.12
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/extensions/AgentClient.d.ts +82 -0
- package/extensions/AgentClient.js +218 -0
- package/extensions/AgentClient.ts +321 -0
- package/extensions/QueryClient.js +85 -37
- package/extensions/QueryClient.ts +82 -34
- package/extensions/index.d.ts +7 -1
- package/extensions/index.js +18 -1
- package/extensions/index.ts +21 -1
- package/package.json +1 -1
- package/sdk/sdk.gen.d.ts +49 -32
- package/sdk/sdk.gen.js +81 -64
- package/sdk/sdk.gen.ts +81 -64
- package/sdk/types.gen.d.ts +94 -73
- package/sdk/types.gen.ts +104 -83
- package/sdk-extensions/AgentClient.d.ts +82 -0
- package/sdk-extensions/AgentClient.js +218 -0
- package/sdk-extensions/AgentClient.ts +321 -0
- package/sdk-extensions/QueryClient.js +85 -37
- package/sdk-extensions/QueryClient.ts +82 -34
- package/sdk-extensions/index.d.ts +7 -1
- package/sdk-extensions/index.js +18 -1
- package/sdk-extensions/index.ts +21 -1
- package/sdk.gen.d.ts +49 -32
- package/sdk.gen.js +81 -64
- package/sdk.gen.ts +81 -64
- package/types.gen.d.ts +94 -73
- package/types.gen.ts +104 -83
|
@@ -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
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
throw new Error(`NDJSON stream reading error: ${error}`);
|
|
105
153
|
}
|
|
106
154
|
// Return aggregated result
|
|
107
155
|
return {
|
|
@@ -75,19 +75,31 @@ export class QueryClient {
|
|
|
75
75
|
mode: options.mode,
|
|
76
76
|
test_mode: options.testMode,
|
|
77
77
|
},
|
|
78
|
+
// For streaming mode, don't parse response - get raw Response object
|
|
79
|
+
...(options.mode === 'stream' ? { parseAs: 'stream' as const } : {}),
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
// Execute the query
|
|
81
82
|
const response = await executeCypherQuery(data)
|
|
82
83
|
|
|
83
|
-
// Check if this is
|
|
84
|
-
|
|
85
|
-
if (response.response) {
|
|
84
|
+
// Check if this is a raw stream response (when parseAs: 'stream')
|
|
85
|
+
if (options.mode === 'stream' && response.response) {
|
|
86
86
|
const contentType = response.response.headers.get('content-type') || ''
|
|
87
|
-
const streamFormat = response.response.headers.get('x-stream-format')
|
|
88
87
|
|
|
89
|
-
if (contentType.includes('application/x-ndjson')
|
|
88
|
+
if (contentType.includes('application/x-ndjson')) {
|
|
90
89
|
return this.parseNDJSONResponse(response.response, graphId)
|
|
90
|
+
} else if (contentType.includes('application/json')) {
|
|
91
|
+
// Fallback: parse JSON manually
|
|
92
|
+
const data = await response.response.json()
|
|
93
|
+
if (data.data !== undefined && data.columns) {
|
|
94
|
+
return {
|
|
95
|
+
data: data.data,
|
|
96
|
+
columns: data.columns,
|
|
97
|
+
row_count: data.row_count || data.data.length,
|
|
98
|
+
execution_time_ms: data.execution_time_ms || 0,
|
|
99
|
+
graph_id: graphId,
|
|
100
|
+
timestamp: data.timestamp || new Date().toISOString(),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
91
103
|
}
|
|
92
104
|
}
|
|
93
105
|
|
|
@@ -118,11 +130,7 @@ export class QueryClient {
|
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
// Use SSE to monitor the operation
|
|
121
|
-
|
|
122
|
-
return this.streamQueryResults(queuedResponse.operation_id, options)
|
|
123
|
-
} else {
|
|
124
|
-
return this.waitForQueryCompletion(queuedResponse.operation_id, options)
|
|
125
|
-
}
|
|
133
|
+
return this.waitForQueryCompletion(queuedResponse.operation_id, options)
|
|
126
134
|
}
|
|
127
135
|
|
|
128
136
|
// Unexpected response format
|
|
@@ -135,37 +143,77 @@ export class QueryClient {
|
|
|
135
143
|
let totalRows = 0
|
|
136
144
|
let executionTimeMs = 0
|
|
137
145
|
|
|
138
|
-
//
|
|
139
|
-
const
|
|
140
|
-
|
|
146
|
+
// Use streaming reader to avoid "body already read" error
|
|
147
|
+
const reader = response.body?.getReader()
|
|
148
|
+
if (!reader) {
|
|
149
|
+
throw new Error('Response body is not readable')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const decoder = new TextDecoder()
|
|
153
|
+
let buffer = ''
|
|
141
154
|
|
|
142
|
-
|
|
143
|
-
|
|
155
|
+
try {
|
|
156
|
+
while (true) {
|
|
157
|
+
const { done, value } = await reader.read()
|
|
158
|
+
if (done) break
|
|
144
159
|
|
|
145
|
-
|
|
146
|
-
const
|
|
160
|
+
buffer += decoder.decode(value, { stream: true })
|
|
161
|
+
const lines = buffer.split('\n')
|
|
162
|
+
buffer = lines.pop() || ''
|
|
147
163
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
164
|
+
for (const line of lines) {
|
|
165
|
+
if (!line.trim()) continue
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const chunk = JSON.parse(line)
|
|
152
169
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
170
|
+
// Extract columns from first chunk
|
|
171
|
+
if (columns === null && chunk.columns) {
|
|
172
|
+
columns = chunk.columns
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Aggregate data rows (NDJSON uses "rows", regular JSON uses "data")
|
|
176
|
+
if (chunk.rows) {
|
|
177
|
+
allData.push(...chunk.rows)
|
|
178
|
+
totalRows += chunk.rows.length
|
|
179
|
+
} else if (chunk.data) {
|
|
180
|
+
allData.push(...chunk.data)
|
|
181
|
+
totalRows += chunk.data.length
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Track execution time (use max from all chunks)
|
|
185
|
+
if (chunk.execution_time_ms) {
|
|
186
|
+
executionTimeMs = Math.max(executionTimeMs, chunk.execution_time_ms)
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error('Failed to parse NDJSON line:', error)
|
|
190
|
+
}
|
|
160
191
|
}
|
|
192
|
+
}
|
|
161
193
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
194
|
+
// Parse any remaining buffer
|
|
195
|
+
if (buffer.trim()) {
|
|
196
|
+
try {
|
|
197
|
+
const chunk = JSON.parse(buffer)
|
|
198
|
+
if (columns === null && chunk.columns) {
|
|
199
|
+
columns = chunk.columns
|
|
200
|
+
}
|
|
201
|
+
if (chunk.rows) {
|
|
202
|
+
allData.push(...chunk.rows)
|
|
203
|
+
totalRows += chunk.rows.length
|
|
204
|
+
} else if (chunk.data) {
|
|
205
|
+
allData.push(...chunk.data)
|
|
206
|
+
totalRows += chunk.data.length
|
|
207
|
+
}
|
|
208
|
+
if (chunk.execution_time_ms) {
|
|
209
|
+
executionTimeMs = Math.max(executionTimeMs, chunk.execution_time_ms)
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('Failed to parse final NDJSON line:', error)
|
|
165
213
|
}
|
|
166
|
-
} catch (error) {
|
|
167
|
-
throw new Error(`Failed to parse NDJSON line: ${error}`)
|
|
168
214
|
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
throw new Error(`NDJSON stream reading error: ${error}`)
|
|
169
217
|
}
|
|
170
218
|
|
|
171
219
|
// Return aggregated result
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { OperationClient } from './OperationClient';
|
|
6
6
|
import { QueryClient } from './QueryClient';
|
|
7
|
+
import { AgentClient } from './AgentClient';
|
|
7
8
|
import { SSEClient } from './SSEClient';
|
|
8
9
|
import { TableIngestClient } from './TableIngestClient';
|
|
9
10
|
import { GraphClient } from './GraphClient';
|
|
@@ -17,6 +18,7 @@ export interface RoboSystemsExtensionConfig {
|
|
|
17
18
|
}
|
|
18
19
|
export declare class RoboSystemsExtensions {
|
|
19
20
|
readonly query: QueryClient;
|
|
21
|
+
readonly agent: AgentClient;
|
|
20
22
|
readonly operations: OperationClient;
|
|
21
23
|
readonly tables: TableIngestClient;
|
|
22
24
|
readonly graphs: GraphClient;
|
|
@@ -37,13 +39,15 @@ export declare class RoboSystemsExtensions {
|
|
|
37
39
|
}
|
|
38
40
|
export * from './OperationClient';
|
|
39
41
|
export * from './QueryClient';
|
|
42
|
+
export * from './AgentClient';
|
|
40
43
|
export * from './SSEClient';
|
|
41
44
|
export * from './TableIngestClient';
|
|
42
45
|
export * from './GraphClient';
|
|
43
|
-
export { OperationClient, QueryClient, SSEClient, TableIngestClient, GraphClient };
|
|
46
|
+
export { OperationClient, QueryClient, AgentClient, SSEClient, TableIngestClient, GraphClient };
|
|
44
47
|
export { useMultipleOperations, useOperation, useQuery, useSDKClients, useStreamingQuery, useTableUpload, } from './hooks';
|
|
45
48
|
export declare const extensions: {
|
|
46
49
|
readonly query: QueryClient;
|
|
50
|
+
readonly agent: AgentClient;
|
|
47
51
|
readonly operations: OperationClient;
|
|
48
52
|
readonly graphs: GraphClient;
|
|
49
53
|
monitorOperation: (operationId: string, onProgress?: (progress: any) => void) => Promise<any>;
|
|
@@ -53,3 +57,5 @@ export declare const extensions: {
|
|
|
53
57
|
export declare const monitorOperation: (operationId: string, onProgress?: (progress: any) => void) => Promise<any>;
|
|
54
58
|
export declare const executeQuery: (graphId: string, query: string, parameters?: Record<string, any>) => Promise<import("./QueryClient").QueryResult>;
|
|
55
59
|
export declare const streamQuery: (graphId: string, query: string, parameters?: Record<string, any>, chunkSize?: number) => AsyncIterableIterator<any>;
|
|
60
|
+
export declare const agentQuery: (graphId: string, message: string, context?: Record<string, any>) => Promise<import("./AgentClient").AgentResult>;
|
|
61
|
+
export declare const analyzeFinancials: (graphId: string, message: string) => Promise<import("./AgentClient").AgentResult>;
|