@mastra/client-js 0.0.0-separate-trace-data-from-component-20250501141108 → 0.0.0-share-agent-metadata-with-cloud-20250718110128

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,194 @@
1
+ import type { WatchEvent } from '@mastra/core/workflows';
2
+
3
+ import type {
4
+ ClientOptions,
5
+ GetVNextNetworkResponse,
6
+ GenerateVNextNetworkResponse,
7
+ LoopVNextNetworkResponse,
8
+ GenerateOrStreamVNextNetworkParams,
9
+ LoopStreamVNextNetworkParams,
10
+ } from '../types';
11
+
12
+ import { BaseResource } from './base';
13
+ import { parseClientRuntimeContext } from '../utils';
14
+ import type { RuntimeContext } from '@mastra/core/runtime-context';
15
+
16
+ const RECORD_SEPARATOR = '\x1E';
17
+
18
+ export class VNextNetwork extends BaseResource {
19
+ constructor(
20
+ options: ClientOptions,
21
+ private networkId: string,
22
+ ) {
23
+ super(options);
24
+ }
25
+
26
+ /**
27
+ * Retrieves details about the network
28
+ * @returns Promise containing vNext network details
29
+ */
30
+ details(): Promise<GetVNextNetworkResponse> {
31
+ return this.request(`/api/networks/v-next/${this.networkId}`);
32
+ }
33
+
34
+ /**
35
+ * Generates a response from the v-next network
36
+ * @param params - Generation parameters including message
37
+ * @returns Promise containing the generated response
38
+ */
39
+ generate(params: GenerateOrStreamVNextNetworkParams): Promise<GenerateVNextNetworkResponse> {
40
+ return this.request(`/api/networks/v-next/${this.networkId}/generate`, {
41
+ method: 'POST',
42
+ body: {
43
+ ...params,
44
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
45
+ },
46
+ });
47
+ }
48
+
49
+ /**
50
+ * Generates a response from the v-next network using multiple primitives
51
+ * @param params - Generation parameters including message
52
+ * @returns Promise containing the generated response
53
+ */
54
+ loop(params: {
55
+ message: string;
56
+ runtimeContext?: RuntimeContext | Record<string, any>;
57
+ }): Promise<LoopVNextNetworkResponse> {
58
+ return this.request(`/api/networks/v-next/${this.networkId}/loop`, {
59
+ method: 'POST',
60
+ body: {
61
+ ...params,
62
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
63
+ },
64
+ });
65
+ }
66
+
67
+ private async *streamProcessor(stream: ReadableStream): AsyncGenerator<WatchEvent, void, unknown> {
68
+ const reader = stream.getReader();
69
+
70
+ // Track if we've finished reading from the stream
71
+ let doneReading = false;
72
+ // Buffer to accumulate partial chunks
73
+ let buffer = '';
74
+
75
+ try {
76
+ while (!doneReading) {
77
+ // Read the next chunk from the stream
78
+ const { done, value } = await reader.read();
79
+ doneReading = done;
80
+
81
+ // Skip processing if we're done and there's no value
82
+ if (done && !value) continue;
83
+
84
+ try {
85
+ // Decode binary data to text
86
+ const decoded = value ? new TextDecoder().decode(value) : '';
87
+
88
+ // Split the combined buffer and new data by record separator
89
+ const chunks = (buffer + decoded).split(RECORD_SEPARATOR);
90
+
91
+ // The last chunk might be incomplete, so save it for the next iteration
92
+ buffer = chunks.pop() || '';
93
+
94
+ // Process complete chunks
95
+ for (const chunk of chunks) {
96
+ if (chunk) {
97
+ // Only process non-empty chunks
98
+ if (typeof chunk === 'string') {
99
+ try {
100
+ const parsedChunk = JSON.parse(chunk);
101
+ yield parsedChunk;
102
+ } catch {
103
+ // Silently ignore parsing errors to maintain stream processing
104
+ // This allows the stream to continue even if one record is malformed
105
+ }
106
+ }
107
+ }
108
+ }
109
+ } catch {
110
+ // Silently ignore parsing errors to maintain stream processing
111
+ // This allows the stream to continue even if one record is malformed
112
+ }
113
+ }
114
+
115
+ // Process any remaining data in the buffer after stream is done
116
+ if (buffer) {
117
+ try {
118
+ yield JSON.parse(buffer);
119
+ } catch {
120
+ // Ignore parsing error for final chunk
121
+ }
122
+ }
123
+ } finally {
124
+ // Always ensure we clean up the reader
125
+ reader.cancel().catch(() => {
126
+ // Ignore cancel errors
127
+ });
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Streams a response from the v-next network
133
+ * @param params - Stream parameters including message
134
+ * @returns Promise containing the results
135
+ */
136
+ async stream(params: GenerateOrStreamVNextNetworkParams, onRecord: (record: WatchEvent) => void) {
137
+ const response: Response = await this.request(`/api/networks/v-next/${this.networkId}/stream`, {
138
+ method: 'POST',
139
+ body: {
140
+ ...params,
141
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
142
+ },
143
+ stream: true,
144
+ });
145
+
146
+ if (!response.ok) {
147
+ throw new Error(`Failed to stream vNext network: ${response.statusText}`);
148
+ }
149
+
150
+ if (!response.body) {
151
+ throw new Error('Response body is null');
152
+ }
153
+
154
+ for await (const record of this.streamProcessor(response.body)) {
155
+ if (typeof record === 'string') {
156
+ onRecord(JSON.parse(record));
157
+ } else {
158
+ onRecord(record);
159
+ }
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Streams a response from the v-next network loop
165
+ * @param params - Stream parameters including message
166
+ * @returns Promise containing the results
167
+ */
168
+ async loopStream(params: LoopStreamVNextNetworkParams, onRecord: (record: WatchEvent) => void) {
169
+ const response: Response = await this.request(`/api/networks/v-next/${this.networkId}/loop-stream`, {
170
+ method: 'POST',
171
+ body: {
172
+ ...params,
173
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
174
+ },
175
+ stream: true,
176
+ });
177
+
178
+ if (!response.ok) {
179
+ throw new Error(`Failed to stream vNext network loop: ${response.statusText}`);
180
+ }
181
+
182
+ if (!response.body) {
183
+ throw new Error('Response body is null');
184
+ }
185
+
186
+ for await (const record of this.streamProcessor(response.body)) {
187
+ if (typeof record === 'string') {
188
+ onRecord(JSON.parse(record));
189
+ } else {
190
+ onRecord(record);
191
+ }
192
+ }
193
+ }
194
+ }
@@ -1,5 +1,16 @@
1
- import type { GetWorkflowResponse, ClientOptions, WorkflowRunResult, GetWorkflowRunsResponse } from '../types';
1
+ import type { RuntimeContext } from '@mastra/core/runtime-context';
2
+ import type {
3
+ ClientOptions,
4
+ GetWorkflowResponse,
5
+ GetWorkflowRunsResponse,
6
+ GetWorkflowRunsParams,
7
+ WorkflowRunResult,
8
+ WorkflowWatchResult,
9
+ GetWorkflowRunByIdResponse,
10
+ GetWorkflowRunExecutionResultResponse,
11
+ } from '../types';
2
12
 
13
+ import { parseClientRuntimeContext } from '../utils';
3
14
  import { BaseResource } from './base';
4
15
 
5
16
  const RECORD_SEPARATOR = '\x1E';
@@ -12,6 +23,77 @@ export class Workflow extends BaseResource {
12
23
  super(options);
13
24
  }
14
25
 
26
+ /**
27
+ * Creates an async generator that processes a readable stream and yields workflow records
28
+ * separated by the Record Separator character (\x1E)
29
+ *
30
+ * @param stream - The readable stream to process
31
+ * @returns An async generator that yields parsed records
32
+ */
33
+ private async *streamProcessor(stream: ReadableStream): AsyncGenerator<WorkflowWatchResult, void, unknown> {
34
+ const reader = stream.getReader();
35
+
36
+ // Track if we've finished reading from the stream
37
+ let doneReading = false;
38
+ // Buffer to accumulate partial chunks
39
+ let buffer = '';
40
+
41
+ try {
42
+ while (!doneReading) {
43
+ // Read the next chunk from the stream
44
+ const { done, value } = await reader.read();
45
+ doneReading = done;
46
+
47
+ // Skip processing if we're done and there's no value
48
+ if (done && !value) continue;
49
+
50
+ try {
51
+ // Decode binary data to text
52
+ const decoded = value ? new TextDecoder().decode(value) : '';
53
+
54
+ // Split the combined buffer and new data by record separator
55
+ const chunks = (buffer + decoded).split(RECORD_SEPARATOR);
56
+
57
+ // The last chunk might be incomplete, so save it for the next iteration
58
+ buffer = chunks.pop() || '';
59
+
60
+ // Process complete chunks
61
+ for (const chunk of chunks) {
62
+ if (chunk) {
63
+ // Only process non-empty chunks
64
+ if (typeof chunk === 'string') {
65
+ try {
66
+ const parsedChunk = JSON.parse(chunk);
67
+ yield parsedChunk;
68
+ } catch {
69
+ // Silently ignore parsing errors to maintain stream processing
70
+ // This allows the stream to continue even if one record is malformed
71
+ }
72
+ }
73
+ }
74
+ }
75
+ } catch {
76
+ // Silently ignore parsing errors to maintain stream processing
77
+ // This allows the stream to continue even if one record is malformed
78
+ }
79
+ }
80
+
81
+ // Process any remaining data in the buffer after stream is done
82
+ if (buffer) {
83
+ try {
84
+ yield JSON.parse(buffer);
85
+ } catch {
86
+ // Ignore parsing error for final chunk
87
+ }
88
+ }
89
+ } finally {
90
+ // Always ensure we clean up the reader
91
+ reader.cancel().catch(() => {
92
+ // Ignore cancel errors
93
+ });
94
+ }
95
+ }
96
+
15
97
  /**
16
98
  * Retrieves details about the workflow
17
99
  * @returns Promise containing workflow details including steps and graphs
@@ -22,28 +104,79 @@ export class Workflow extends BaseResource {
22
104
 
23
105
  /**
24
106
  * Retrieves all runs for a workflow
107
+ * @param params - Parameters for filtering runs
25
108
  * @returns Promise containing workflow runs array
26
109
  */
27
- runs(): Promise<GetWorkflowRunsResponse> {
28
- return this.request(`/api/workflows/${this.workflowId}/runs`);
110
+ runs(params?: GetWorkflowRunsParams): Promise<GetWorkflowRunsResponse> {
111
+ const searchParams = new URLSearchParams();
112
+ if (params?.fromDate) {
113
+ searchParams.set('fromDate', params.fromDate.toISOString());
114
+ }
115
+ if (params?.toDate) {
116
+ searchParams.set('toDate', params.toDate.toISOString());
117
+ }
118
+ if (params?.limit !== null && params?.limit !== undefined && !isNaN(Number(params?.limit))) {
119
+ searchParams.set('limit', String(params.limit));
120
+ }
121
+ if (params?.offset !== null && params?.offset !== undefined && !isNaN(Number(params?.offset))) {
122
+ searchParams.set('offset', String(params.offset));
123
+ }
124
+ if (params?.resourceId) {
125
+ searchParams.set('resourceId', params.resourceId);
126
+ }
127
+
128
+ if (searchParams.size) {
129
+ return this.request(`/api/workflows/${this.workflowId}/runs?${searchParams}`);
130
+ } else {
131
+ return this.request(`/api/workflows/${this.workflowId}/runs`);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Retrieves a specific workflow run by its ID
137
+ * @param runId - The ID of the workflow run to retrieve
138
+ * @returns Promise containing the workflow run details
139
+ */
140
+ runById(runId: string): Promise<GetWorkflowRunByIdResponse> {
141
+ return this.request(`/api/workflows/${this.workflowId}/runs/${runId}`);
29
142
  }
30
143
 
31
144
  /**
32
- * @deprecated Use `startAsync` instead
33
- * Executes the workflow with the provided parameters
34
- * @param params - Parameters required for workflow execution
35
- * @returns Promise containing the workflow execution results
145
+ * Retrieves the execution result for a specific workflow run by its ID
146
+ * @param runId - The ID of the workflow run to retrieve the execution result for
147
+ * @returns Promise containing the workflow run execution result
148
+ */
149
+ runExecutionResult(runId: string): Promise<GetWorkflowRunExecutionResultResponse> {
150
+ return this.request(`/api/workflows/${this.workflowId}/runs/${runId}/execution-result`);
151
+ }
152
+
153
+ /**
154
+ * Cancels a specific workflow run by its ID
155
+ * @param runId - The ID of the workflow run to cancel
156
+ * @returns Promise containing a success message
157
+ */
158
+ cancelRun(runId: string): Promise<{ message: string }> {
159
+ return this.request(`/api/workflows/${this.workflowId}/runs/${runId}/cancel`, {
160
+ method: 'POST',
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Sends an event to a specific workflow run by its ID
166
+ * @param params - Object containing the runId, event and data
167
+ * @returns Promise containing a success message
36
168
  */
37
- execute(params: Record<string, any>): Promise<WorkflowRunResult> {
38
- return this.request(`/api/workflows/${this.workflowId}/execute`, {
169
+ sendRunEvent(params: { runId: string; event: string; data: unknown }): Promise<{ message: string }> {
170
+ return this.request(`/api/workflows/${this.workflowId}/runs/${params.runId}/send-event`, {
39
171
  method: 'POST',
40
- body: params,
172
+ body: { event: params.event, data: params.data },
41
173
  });
42
174
  }
43
175
 
44
176
  /**
45
177
  * Creates a new workflow run
46
- * @returns Promise containing the generated run ID
178
+ * @param params - Optional object containing the optional runId
179
+ * @returns Promise containing the runId of the created run
47
180
  */
48
181
  createRun(params?: { runId?: string }): Promise<{ runId: string }> {
49
182
  const searchParams = new URLSearchParams();
@@ -52,150 +185,167 @@ export class Workflow extends BaseResource {
52
185
  searchParams.set('runId', params.runId);
53
186
  }
54
187
 
55
- return this.request(`/api/workflows/${this.workflowId}/createRun?${searchParams.toString()}`, {
188
+ return this.request(`/api/workflows/${this.workflowId}/create-run?${searchParams.toString()}`, {
56
189
  method: 'POST',
57
190
  });
58
191
  }
59
192
 
60
193
  /**
61
194
  * Starts a workflow run synchronously without waiting for the workflow to complete
62
- * @param params - Object containing the runId and triggerData
195
+ * @param params - Object containing the runId, inputData and runtimeContext
63
196
  * @returns Promise containing success message
64
197
  */
65
- start(params: { runId: string; triggerData: Record<string, any> }): Promise<{ message: string }> {
198
+ start(params: {
199
+ runId: string;
200
+ inputData: Record<string, any>;
201
+ runtimeContext?: RuntimeContext | Record<string, any>;
202
+ }): Promise<{ message: string }> {
203
+ const runtimeContext = parseClientRuntimeContext(params.runtimeContext);
66
204
  return this.request(`/api/workflows/${this.workflowId}/start?runId=${params.runId}`, {
67
205
  method: 'POST',
68
- body: params?.triggerData,
206
+ body: { inputData: params?.inputData, runtimeContext },
69
207
  });
70
208
  }
71
209
 
72
210
  /**
73
211
  * Resumes a suspended workflow step synchronously without waiting for the workflow to complete
74
- * @param stepId - ID of the step to resume
75
- * @param runId - ID of the workflow run
76
- * @param context - Context to resume the workflow with
77
- * @returns Promise containing the workflow resume results
212
+ * @param params - Object containing the runId, step, resumeData and runtimeContext
213
+ * @returns Promise containing success message
78
214
  */
79
215
  resume({
80
- stepId,
216
+ step,
81
217
  runId,
82
- context,
218
+ resumeData,
219
+ ...rest
83
220
  }: {
84
- stepId: string;
221
+ step: string | string[];
85
222
  runId: string;
86
- context: Record<string, any>;
223
+ resumeData?: Record<string, any>;
224
+ runtimeContext?: RuntimeContext | Record<string, any>;
87
225
  }): Promise<{ message: string }> {
226
+ const runtimeContext = parseClientRuntimeContext(rest.runtimeContext);
88
227
  return this.request(`/api/workflows/${this.workflowId}/resume?runId=${runId}`, {
89
228
  method: 'POST',
229
+ stream: true,
90
230
  body: {
91
- stepId,
92
- context,
231
+ step,
232
+ resumeData,
233
+ runtimeContext,
93
234
  },
94
235
  });
95
236
  }
96
237
 
97
238
  /**
98
239
  * Starts a workflow run asynchronously and returns a promise that resolves when the workflow is complete
99
- * @param params - Object containing the optional runId and triggerData
240
+ * @param params - Object containing the optional runId, inputData and runtimeContext
100
241
  * @returns Promise containing the workflow execution results
101
242
  */
102
- startAsync(params: { runId?: string; triggerData: Record<string, any> }): Promise<WorkflowRunResult> {
243
+ startAsync(params: {
244
+ runId?: string;
245
+ inputData: Record<string, any>;
246
+ runtimeContext?: RuntimeContext | Record<string, any>;
247
+ }): Promise<WorkflowRunResult> {
103
248
  const searchParams = new URLSearchParams();
104
249
 
105
250
  if (!!params?.runId) {
106
251
  searchParams.set('runId', params.runId);
107
252
  }
108
253
 
254
+ const runtimeContext = parseClientRuntimeContext(params.runtimeContext);
255
+
109
256
  return this.request(`/api/workflows/${this.workflowId}/start-async?${searchParams.toString()}`, {
110
257
  method: 'POST',
111
- body: params?.triggerData,
258
+ body: { inputData: params.inputData, runtimeContext },
112
259
  });
113
260
  }
114
261
 
115
262
  /**
116
- * Resumes a suspended workflow step asynchronously and returns a promise that resolves when the workflow is complete
117
- * @param params - Object containing the runId, stepId, and context
118
- * @returns Promise containing the workflow resume results
263
+ * Starts a workflow run and returns a stream
264
+ * @param params - Object containing the optional runId, inputData and runtimeContext
265
+ * @returns Promise containing the workflow execution results
119
266
  */
120
- resumeAsync(params: { runId: string; stepId: string; context: Record<string, any> }): Promise<WorkflowRunResult> {
121
- return this.request(`/api/workflows/${this.workflowId}/resume-async?runId=${params.runId}`, {
122
- method: 'POST',
123
- body: {
124
- stepId: params.stepId,
125
- context: params.context,
126
- },
127
- });
128
- }
267
+ async stream(params: { runId?: string; inputData: Record<string, any>; runtimeContext?: RuntimeContext }) {
268
+ const searchParams = new URLSearchParams();
129
269
 
130
- /**
131
- * Creates an async generator that processes a readable stream and yields records
132
- * separated by the Record Separator character (\x1E)
133
- *
134
- * @param stream - The readable stream to process
135
- * @returns An async generator that yields parsed records
136
- */
137
- private async *streamProcessor(stream: ReadableStream): AsyncGenerator<WorkflowRunResult, void, unknown> {
138
- const reader = stream.getReader();
270
+ if (!!params?.runId) {
271
+ searchParams.set('runId', params.runId);
272
+ }
139
273
 
140
- // Track if we've finished reading from the stream
141
- let doneReading = false;
142
- // Buffer to accumulate partial chunks
143
- let buffer = '';
274
+ const runtimeContext = parseClientRuntimeContext(params.runtimeContext);
275
+ const response: Response = await this.request(
276
+ `/api/workflows/${this.workflowId}/stream?${searchParams.toString()}`,
277
+ {
278
+ method: 'POST',
279
+ body: { inputData: params.inputData, runtimeContext },
280
+ stream: true,
281
+ },
282
+ );
144
283
 
145
- try {
146
- while (!doneReading) {
147
- // Read the next chunk from the stream
148
- const { done, value } = await reader.read();
149
- doneReading = done;
284
+ if (!response.ok) {
285
+ throw new Error(`Failed to stream vNext workflow: ${response.statusText}`);
286
+ }
150
287
 
151
- // Skip processing if we're done and there's no value
152
- if (done && !value) continue;
288
+ if (!response.body) {
289
+ throw new Error('Response body is null');
290
+ }
291
+
292
+ //using undefined instead of empty string to avoid parsing errors
293
+ let failedChunk: string | undefined = undefined;
153
294
 
295
+ // Create a transform stream that processes the response body
296
+ const transformStream = new TransformStream<ArrayBuffer, { type: string; payload: any }>({
297
+ start() {},
298
+ async transform(chunk, controller) {
154
299
  try {
155
300
  // Decode binary data to text
156
- const decoded = value ? new TextDecoder().decode(value) : '';
157
-
158
- // Split the combined buffer and new data by record separator
159
- const chunks = (buffer + decoded).split(RECORD_SEPARATOR);
301
+ const decoded = new TextDecoder().decode(chunk);
160
302
 
161
- // The last chunk might be incomplete, so save it for the next iteration
162
- buffer = chunks.pop() || '';
303
+ // Split by record separator
304
+ const chunks = decoded.split(RECORD_SEPARATOR);
163
305
 
164
- // Process complete chunks
306
+ // Process each chunk
165
307
  for (const chunk of chunks) {
166
308
  if (chunk) {
167
- // Only process non-empty chunks
168
- if (typeof chunk === 'string') {
169
- try {
170
- const parsedChunk = JSON.parse(chunk);
171
- yield parsedChunk;
172
- } catch {
173
- // Silently ignore parsing errors to maintain stream processing
174
- // This allows the stream to continue even if one record is malformed
175
- }
309
+ const newChunk: string = failedChunk ? failedChunk + chunk : chunk;
310
+ try {
311
+ const parsedChunk = JSON.parse(newChunk);
312
+ controller.enqueue(parsedChunk);
313
+ failedChunk = undefined;
314
+ } catch (error) {
315
+ failedChunk = newChunk;
176
316
  }
177
317
  }
178
318
  }
179
319
  } catch {
180
- // Silently ignore parsing errors to maintain stream processing
181
- // This allows the stream to continue even if one record is malformed
320
+ // Silently ignore processing errors
182
321
  }
183
- }
322
+ },
323
+ });
184
324
 
185
- // Process any remaining data in the buffer after stream is done
186
- if (buffer) {
187
- try {
188
- yield JSON.parse(buffer);
189
- } catch {
190
- // Ignore parsing error for final chunk
191
- }
192
- }
193
- } finally {
194
- // Always ensure we clean up the reader
195
- reader.cancel().catch(() => {
196
- // Ignore cancel errors
197
- });
198
- }
325
+ // Pipe the response body through the transform stream
326
+ return response.body.pipeThrough(transformStream);
327
+ }
328
+
329
+ /**
330
+ * Resumes a suspended workflow step asynchronously and returns a promise that resolves when the workflow is complete
331
+ * @param params - Object containing the runId, step, resumeData and runtimeContext
332
+ * @returns Promise containing the workflow resume results
333
+ */
334
+ resumeAsync(params: {
335
+ runId: string;
336
+ step: string | string[];
337
+ resumeData?: Record<string, any>;
338
+ runtimeContext?: RuntimeContext | Record<string, any>;
339
+ }): Promise<WorkflowRunResult> {
340
+ const runtimeContext = parseClientRuntimeContext(params.runtimeContext);
341
+ return this.request(`/api/workflows/${this.workflowId}/resume-async?runId=${params.runId}`, {
342
+ method: 'POST',
343
+ body: {
344
+ step: params.step,
345
+ resumeData: params.resumeData,
346
+ runtimeContext,
347
+ },
348
+ });
199
349
  }
200
350
 
201
351
  /**
@@ -203,7 +353,7 @@ export class Workflow extends BaseResource {
203
353
  * @param runId - Optional run ID to filter the watch stream
204
354
  * @returns AsyncGenerator that yields parsed records from the workflow watch stream
205
355
  */
206
- async watch({ runId }: { runId?: string }, onRecord: (record: WorkflowRunResult) => void) {
356
+ async watch({ runId }: { runId?: string }, onRecord: (record: WorkflowWatchResult) => void) {
207
357
  const response: Response = await this.request(`/api/workflows/${this.workflowId}/watch?runId=${runId}`, {
208
358
  stream: true,
209
359
  });
@@ -217,7 +367,35 @@ export class Workflow extends BaseResource {
217
367
  }
218
368
 
219
369
  for await (const record of this.streamProcessor(response.body)) {
220
- onRecord(record);
370
+ if (typeof record === 'string') {
371
+ onRecord(JSON.parse(record));
372
+ } else {
373
+ onRecord(record);
374
+ }
221
375
  }
222
376
  }
377
+
378
+ /**
379
+ * Creates a new ReadableStream from an iterable or async iterable of objects,
380
+ * serializing each as JSON and separating them with the record separator (\x1E).
381
+ *
382
+ * @param records - An iterable or async iterable of objects to stream
383
+ * @returns A ReadableStream emitting the records as JSON strings separated by the record separator
384
+ */
385
+ static createRecordStream(records: Iterable<any> | AsyncIterable<any>): ReadableStream {
386
+ const encoder = new TextEncoder();
387
+ return new ReadableStream({
388
+ async start(controller) {
389
+ try {
390
+ for await (const record of records as AsyncIterable<any>) {
391
+ const json = JSON.stringify(record) + RECORD_SEPARATOR;
392
+ controller.enqueue(encoder.encode(json));
393
+ }
394
+ controller.close();
395
+ } catch (err) {
396
+ controller.error(err);
397
+ }
398
+ },
399
+ });
400
+ }
223
401
  }