@mastra/client-js 0.0.0-switch-to-core-20250424015131 → 0.0.0-vnext-inngest-20250506121901

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,219 @@
1
+ // Cross-platform UUID generation function
2
+ import { AbstractAgent, EventType } from '@ag-ui/client';
3
+ import type {
4
+ BaseEvent,
5
+ RunAgentInput,
6
+ AgentConfig,
7
+ RunStartedEvent,
8
+ RunFinishedEvent,
9
+ TextMessageStartEvent,
10
+ TextMessageContentEvent,
11
+ TextMessageEndEvent,
12
+ Message,
13
+ ToolCallStartEvent,
14
+ ToolCallArgsEvent,
15
+ ToolCallEndEvent,
16
+ } from '@ag-ui/client';
17
+ import type { CoreMessage } from '@mastra/core';
18
+ import { Observable } from 'rxjs';
19
+ import type { Agent } from '../resources/agent';
20
+
21
+ interface MastraAgentConfig extends AgentConfig {
22
+ agent: Agent;
23
+ agentId: string;
24
+ resourceId?: string;
25
+ }
26
+
27
+ export class AGUIAdapter extends AbstractAgent {
28
+ agent: Agent;
29
+ resourceId?: string;
30
+ constructor({ agent, agentId, resourceId, ...rest }: MastraAgentConfig) {
31
+ super({
32
+ agentId,
33
+ ...rest,
34
+ });
35
+ this.agent = agent;
36
+ this.resourceId = resourceId;
37
+ }
38
+
39
+ protected run(input: RunAgentInput): Observable<BaseEvent> {
40
+ return new Observable<BaseEvent>(subscriber => {
41
+ const convertedMessages = convertMessagesToMastraMessages(input.messages);
42
+
43
+ subscriber.next({
44
+ type: EventType.RUN_STARTED,
45
+ threadId: input.threadId,
46
+ runId: input.runId,
47
+ } as RunStartedEvent);
48
+
49
+ this.agent
50
+ .stream({
51
+ threadId: input.threadId,
52
+ resourceId: this.resourceId ?? '',
53
+ runId: input.runId,
54
+ messages: convertedMessages,
55
+ clientTools: input.tools.reduce(
56
+ (acc, tool) => {
57
+ acc[tool.name as string] = {
58
+ id: tool.name,
59
+ description: tool.description,
60
+ inputSchema: tool.parameters,
61
+ };
62
+ return acc;
63
+ },
64
+ {} as Record<string, any>,
65
+ ),
66
+ })
67
+ .then(response => {
68
+ let currentMessageId: string | undefined = undefined;
69
+ return response.processDataStream({
70
+ onTextPart: text => {
71
+ if (currentMessageId === undefined) {
72
+ currentMessageId = generateUUID();
73
+
74
+ const message: TextMessageStartEvent = {
75
+ type: EventType.TEXT_MESSAGE_START,
76
+ messageId: currentMessageId,
77
+ role: 'assistant',
78
+ };
79
+ subscriber.next(message);
80
+ }
81
+
82
+ const message: TextMessageContentEvent = {
83
+ type: EventType.TEXT_MESSAGE_CONTENT,
84
+ messageId: currentMessageId,
85
+ delta: text,
86
+ };
87
+ subscriber.next(message);
88
+ },
89
+ onFinishMessagePart: message => {
90
+ console.log('onFinishMessagePart', message);
91
+ if (currentMessageId !== undefined) {
92
+ const message: TextMessageEndEvent = {
93
+ type: EventType.TEXT_MESSAGE_END,
94
+ messageId: currentMessageId,
95
+ };
96
+ subscriber.next(message);
97
+ }
98
+ // Emit run finished event
99
+ subscriber.next({
100
+ type: EventType.RUN_FINISHED,
101
+ threadId: input.threadId,
102
+ runId: input.runId,
103
+ } as RunFinishedEvent);
104
+
105
+ // Complete the observable
106
+ subscriber.complete();
107
+ },
108
+ onToolCallPart(streamPart) {
109
+ const parentMessageId = currentMessageId || generateUUID();
110
+ subscriber.next({
111
+ type: EventType.TOOL_CALL_START,
112
+ toolCallId: streamPart.toolCallId,
113
+ toolCallName: streamPart.toolName,
114
+ parentMessageId,
115
+ } as ToolCallStartEvent);
116
+
117
+ subscriber.next({
118
+ type: EventType.TOOL_CALL_ARGS,
119
+ toolCallId: streamPart.toolCallId,
120
+ delta: JSON.stringify(streamPart.args),
121
+ parentMessageId,
122
+ } as ToolCallArgsEvent);
123
+
124
+ subscriber.next({
125
+ type: EventType.TOOL_CALL_END,
126
+ toolCallId: streamPart.toolCallId,
127
+ parentMessageId,
128
+ } as ToolCallEndEvent);
129
+ },
130
+ });
131
+ })
132
+ .catch(error => {
133
+ console.log('error', error);
134
+ // Handle error
135
+ subscriber.error(error);
136
+ });
137
+
138
+ return () => {};
139
+ });
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Generates a UUID v4 that works in both browser and Node.js environments
145
+ */
146
+ export function generateUUID(): string {
147
+ // Use crypto.randomUUID() if available (Node.js environment or modern browsers)
148
+ if (typeof crypto !== 'undefined') {
149
+ // Browser crypto API or Node.js crypto global
150
+ if (typeof crypto.randomUUID === 'function') {
151
+ return crypto.randomUUID();
152
+ }
153
+ // Fallback for older browsers
154
+ if (typeof crypto.getRandomValues === 'function') {
155
+ const buffer = new Uint8Array(16);
156
+ crypto.getRandomValues(buffer);
157
+ // Set version (4) and variant (8, 9, A, or B)
158
+ buffer[6] = (buffer[6]! & 0x0f) | 0x40; // version 4
159
+ buffer[8] = (buffer[8]! & 0x3f) | 0x80; // variant
160
+
161
+ // Convert to hex string in UUID format
162
+ let hex = '';
163
+ for (let i = 0; i < 16; i++) {
164
+ hex += buffer[i]!.toString(16).padStart(2, '0');
165
+ // Add hyphens at standard positions
166
+ if (i === 3 || i === 5 || i === 7 || i === 9) hex += '-';
167
+ }
168
+ return hex;
169
+ }
170
+ }
171
+
172
+ // Last resort fallback (less secure but works everywhere)
173
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
174
+ const r = (Math.random() * 16) | 0;
175
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
176
+ return v.toString(16);
177
+ });
178
+ }
179
+
180
+ export function convertMessagesToMastraMessages(messages: Message[]): CoreMessage[] {
181
+ const result: CoreMessage[] = [];
182
+
183
+ for (const message of messages) {
184
+ if (message.role === 'assistant') {
185
+ const parts: any[] = message.content ? [{ type: 'text', text: message.content }] : [];
186
+ for (const toolCall of message.toolCalls ?? []) {
187
+ parts.push({
188
+ type: 'tool-call',
189
+ toolCallId: toolCall.id,
190
+ toolName: toolCall.function.name,
191
+ args: JSON.parse(toolCall.function.arguments),
192
+ });
193
+ }
194
+ result.push({
195
+ role: 'assistant',
196
+ content: parts,
197
+ });
198
+ } else if (message.role === 'user') {
199
+ result.push({
200
+ role: 'user',
201
+ content: message.content || '',
202
+ });
203
+ } else if (message.role === 'tool') {
204
+ result.push({
205
+ role: 'tool',
206
+ content: [
207
+ {
208
+ type: 'tool-result',
209
+ toolCallId: message.toolCallId,
210
+ toolName: 'unknown',
211
+ result: message.content,
212
+ },
213
+ ],
214
+ });
215
+ }
216
+ }
217
+
218
+ return result;
219
+ }
package/src/client.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { Agent, MemoryThread, Tool, Workflow, Vector, BaseResource, Network } from './resources';
1
+ import type { AbstractAgent } from '@ag-ui/client';
2
+ import { AGUIAdapter } from './adapters/agui';
3
+ import { Agent, MemoryThread, Tool, Workflow, Vector, BaseResource, Network, VNextWorkflow } from './resources';
2
4
  import type {
3
5
  ClientOptions,
4
6
  CreateMemoryThreadParams,
@@ -13,6 +15,7 @@ import type {
13
15
  GetTelemetryParams,
14
16
  GetTelemetryResponse,
15
17
  GetToolResponse,
18
+ GetVNextWorkflowResponse,
16
19
  GetWorkflowResponse,
17
20
  SaveMessageToMemoryParams,
18
21
  SaveMessageToMemoryResponse,
@@ -31,6 +34,25 @@ export class MastraClient extends BaseResource {
31
34
  return this.request('/api/agents');
32
35
  }
33
36
 
37
+ public async getAGUI({ resourceId }: { resourceId: string }): Promise<Record<string, AbstractAgent>> {
38
+ const agents = await this.getAgents();
39
+
40
+ return Object.entries(agents).reduce(
41
+ (acc, [agentId]) => {
42
+ const agent = this.getAgent(agentId);
43
+
44
+ acc[agentId] = new AGUIAdapter({
45
+ agentId,
46
+ agent,
47
+ resourceId,
48
+ });
49
+
50
+ return acc;
51
+ },
52
+ {} as Record<string, AbstractAgent>,
53
+ );
54
+ }
55
+
34
56
  /**
35
57
  * Gets an agent instance by ID
36
58
  * @param agentId - ID of the agent to retrieve
@@ -121,6 +143,23 @@ export class MastraClient extends BaseResource {
121
143
  return new Workflow(this.options, workflowId);
122
144
  }
123
145
 
146
+ /**
147
+ * Retrieves all available vNext workflows
148
+ * @returns Promise containing map of vNext workflow IDs to vNext workflow details
149
+ */
150
+ public getVNextWorkflows(): Promise<Record<string, GetVNextWorkflowResponse>> {
151
+ return this.request('/api/workflows/v-next');
152
+ }
153
+
154
+ /**
155
+ * Gets a vNext workflow instance by ID
156
+ * @param workflowId - ID of the vNext workflow to retrieve
157
+ * @returns vNext Workflow instance
158
+ */
159
+ public getVNextWorkflow(workflowId: string) {
160
+ return new VNextWorkflow(this.options, workflowId);
161
+ }
162
+
124
163
  /**
125
164
  * Gets a vector instance by name
126
165
  * @param vectorName - Name of the vector to retrieve
@@ -162,7 +201,7 @@ export class MastraClient extends BaseResource {
162
201
  * @returns Promise containing telemetry data
163
202
  */
164
203
  public getTelemetry(params?: GetTelemetryParams): Promise<GetTelemetryResponse> {
165
- const { name, scope, page, perPage, attribute } = params || {};
204
+ const { name, scope, page, perPage, attribute, fromDate, toDate } = params || {};
166
205
  const _attribute = attribute ? Object.entries(attribute).map(([key, value]) => `${key}:${value}`) : [];
167
206
 
168
207
  const searchParams = new URLSearchParams();
@@ -187,6 +226,12 @@ export class MastraClient extends BaseResource {
187
226
  searchParams.set('attribute', _attribute);
188
227
  }
189
228
  }
229
+ if (fromDate) {
230
+ searchParams.set('fromDate', fromDate.toISOString());
231
+ }
232
+ if (toDate) {
233
+ searchParams.set('toDate', toDate.toISOString());
234
+ }
190
235
 
191
236
  if (searchParams.size) {
192
237
  return this.request(`/api/telemetry?${searchParams}`);
package/src/index.test.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { MessageType } from '@mastra/core';
2
1
  import { describe, expect, beforeEach, it, vi } from 'vitest';
3
2
 
4
3
  import { MastraClient } from './client';
@@ -489,7 +488,7 @@ describe('MastraClient Resources', () => {
489
488
  const result = await memoryThread.update({
490
489
  title: 'Updated Thread',
491
490
  metadata: { updated: true },
492
- resourceid: 'test-resource',
491
+ resourceId: 'test-resource',
493
492
  });
494
493
  expect(result).toEqual(mockResponse);
495
494
  expect(global.fetch).toHaveBeenCalledWith(
@@ -536,6 +535,7 @@ describe('MastraClient Resources', () => {
536
535
  content: 'test',
537
536
  role: 'user' as const,
538
537
  threadId: 'test-thread',
538
+ resourceId: 'test-resource',
539
539
  createdAt: new Date('2025-03-26T10:40:55.116Z'),
540
540
  },
541
541
  ];
@@ -584,10 +584,10 @@ describe('MastraClient Resources', () => {
584
584
  it('should execute tool', async () => {
585
585
  const mockResponse = { data: 'test' };
586
586
  mockFetchResponse(mockResponse);
587
- const result = await tool.execute({ data: '' });
587
+ const result = await tool.execute({ data: '', runId: 'test-run-id' });
588
588
  expect(result).toEqual(mockResponse);
589
589
  expect(global.fetch).toHaveBeenCalledWith(
590
- `${clientOptions.baseUrl}/api/tools/test-tool/execute`,
590
+ `${clientOptions.baseUrl}/api/tools/test-tool/execute?runId=test-run-id`,
591
591
  expect.objectContaining({
592
592
  method: 'POST',
593
593
  headers: expect.objectContaining({
@@ -1,8 +1,8 @@
1
+ import { processDataStream } from '@ai-sdk/ui-utils';
1
2
  import type { GenerateReturn } from '@mastra/core';
2
3
  import type { JSONSchema7 } from 'json-schema';
3
4
  import { ZodSchema } from 'zod';
4
5
  import { zodToJsonSchema } from 'zod-to-json-schema';
5
- import { processDataStream } from '@ai-sdk/ui-utils';
6
6
 
7
7
  import type {
8
8
  GenerateParams,
@@ -29,7 +29,6 @@ export class AgentTool extends BaseResource {
29
29
  * @param params - Parameters required for tool execution
30
30
  * @returns Promise containing tool execution results
31
31
  */
32
- /** @deprecated use CreateRun/startRun */
33
32
  execute(params: { data: any }): Promise<any> {
34
33
  return this.request(`/api/agents/${this.agentId}/tools/${this.toolId}/execute`, {
35
34
  method: 'POST',
@@ -1,4 +1,4 @@
1
- import type { RequestFunction, RequestOptions, ClientOptions } from '../types';
1
+ import type { RequestOptions, ClientOptions } from '../types';
2
2
 
3
3
  export class BaseResource {
4
4
  readonly options: ClientOptions;
@@ -5,3 +5,4 @@ export * from './vector';
5
5
  export * from './workflow';
6
6
  export * from './tool';
7
7
  export * from './base';
8
+ export * from './vnext-workflow';
@@ -1,3 +1,4 @@
1
+ import { processDataStream } from '@ai-sdk/ui-utils';
1
2
  import type { GenerateReturn } from '@mastra/core';
2
3
  import type { JSONSchema7 } from 'json-schema';
3
4
  import { ZodSchema } from 'zod';
@@ -6,7 +7,6 @@ import { zodToJsonSchema } from 'zod-to-json-schema';
6
7
  import type { GenerateParams, ClientOptions, StreamParams, GetNetworkResponse } from '../types';
7
8
 
8
9
  import { BaseResource } from './base';
9
- import { processDataStream } from '@ai-sdk/ui-utils';
10
10
 
11
11
  export class Network extends BaseResource {
12
12
  constructor(
@@ -23,10 +23,16 @@ export class Tool extends BaseResource {
23
23
  * @param params - Parameters required for tool execution
24
24
  * @returns Promise containing the tool execution results
25
25
  */
26
- execute(params: { data: any }): Promise<any> {
27
- return this.request(`/api/tools/${this.toolId}/execute`, {
26
+ execute(params: { data: any; runId?: string }): Promise<any> {
27
+ const url = new URLSearchParams();
28
+
29
+ if (params.runId) {
30
+ url.set('runId', params.runId);
31
+ }
32
+
33
+ return this.request(`/api/tools/${this.toolId}/execute?${url.toString()}`, {
28
34
  method: 'POST',
29
- body: params,
35
+ body: params.data,
30
36
  });
31
37
  }
32
38
  }
@@ -0,0 +1,257 @@
1
+ import type { RuntimeContext } from '@mastra/core/runtime-context';
2
+ import type {
3
+ ClientOptions,
4
+ GetVNextWorkflowResponse,
5
+ GetWorkflowRunsParams,
6
+ GetWorkflowRunsResponse,
7
+ VNextWorkflowRunResult,
8
+ VNextWorkflowWatchResult,
9
+ } from '../types';
10
+
11
+ import { BaseResource } from './base';
12
+
13
+ const RECORD_SEPARATOR = '\x1E';
14
+
15
+ export class VNextWorkflow extends BaseResource {
16
+ constructor(
17
+ options: ClientOptions,
18
+ private workflowId: string,
19
+ ) {
20
+ super(options);
21
+ }
22
+
23
+ /**
24
+ * Creates an async generator that processes a readable stream and yields vNext workflow records
25
+ * separated by the Record Separator character (\x1E)
26
+ *
27
+ * @param stream - The readable stream to process
28
+ * @returns An async generator that yields parsed records
29
+ */
30
+ private async *streamProcessor(stream: ReadableStream): AsyncGenerator<VNextWorkflowWatchResult, void, unknown> {
31
+ const reader = stream.getReader();
32
+
33
+ // Track if we've finished reading from the stream
34
+ let doneReading = false;
35
+ // Buffer to accumulate partial chunks
36
+ let buffer = '';
37
+
38
+ try {
39
+ while (!doneReading) {
40
+ // Read the next chunk from the stream
41
+ const { done, value } = await reader.read();
42
+ doneReading = done;
43
+
44
+ // Skip processing if we're done and there's no value
45
+ if (done && !value) continue;
46
+
47
+ try {
48
+ // Decode binary data to text
49
+ const decoded = value ? new TextDecoder().decode(value) : '';
50
+
51
+ // Split the combined buffer and new data by record separator
52
+ const chunks = (buffer + decoded).split(RECORD_SEPARATOR);
53
+
54
+ // The last chunk might be incomplete, so save it for the next iteration
55
+ buffer = chunks.pop() || '';
56
+
57
+ // Process complete chunks
58
+ for (const chunk of chunks) {
59
+ if (chunk) {
60
+ // Only process non-empty chunks
61
+ if (typeof chunk === 'string') {
62
+ try {
63
+ const parsedChunk = JSON.parse(chunk);
64
+ yield parsedChunk;
65
+ } catch {
66
+ // Silently ignore parsing errors to maintain stream processing
67
+ // This allows the stream to continue even if one record is malformed
68
+ }
69
+ }
70
+ }
71
+ }
72
+ } catch {
73
+ // Silently ignore parsing errors to maintain stream processing
74
+ // This allows the stream to continue even if one record is malformed
75
+ }
76
+ }
77
+
78
+ // Process any remaining data in the buffer after stream is done
79
+ if (buffer) {
80
+ try {
81
+ yield JSON.parse(buffer);
82
+ } catch {
83
+ // Ignore parsing error for final chunk
84
+ }
85
+ }
86
+ } finally {
87
+ // Always ensure we clean up the reader
88
+ reader.cancel().catch(() => {
89
+ // Ignore cancel errors
90
+ });
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Retrieves details about the vNext workflow
96
+ * @returns Promise containing vNext workflow details including steps and graphs
97
+ */
98
+ details(): Promise<GetVNextWorkflowResponse> {
99
+ return this.request(`/api/workflows/v-next/${this.workflowId}`);
100
+ }
101
+
102
+ /**
103
+ * Retrieves all runs for a vNext workflow
104
+ * @param params - Parameters for filtering runs
105
+ * @returns Promise containing vNext workflow runs array
106
+ */
107
+ runs(params?: GetWorkflowRunsParams): Promise<GetWorkflowRunsResponse> {
108
+ const searchParams = new URLSearchParams();
109
+ if (params?.fromDate) {
110
+ searchParams.set('fromDate', params.fromDate.toISOString());
111
+ }
112
+ if (params?.toDate) {
113
+ searchParams.set('toDate', params.toDate.toISOString());
114
+ }
115
+ if (params?.limit) {
116
+ searchParams.set('limit', String(params.limit));
117
+ }
118
+ if (params?.offset) {
119
+ searchParams.set('offset', String(params.offset));
120
+ }
121
+ if (params?.resourceId) {
122
+ searchParams.set('resourceId', params.resourceId);
123
+ }
124
+
125
+ if (searchParams.size) {
126
+ return this.request(`/api/workflows/v-next/${this.workflowId}/runs?${searchParams}`);
127
+ } else {
128
+ return this.request(`/api/workflows/v-next/${this.workflowId}/runs`);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Creates a new vNext workflow run
134
+ * @param params - Optional object containing the optional runId
135
+ * @returns Promise containing the runId of the created run
136
+ */
137
+ createRun(params?: { runId?: string }): Promise<{ runId: string }> {
138
+ const searchParams = new URLSearchParams();
139
+
140
+ if (!!params?.runId) {
141
+ searchParams.set('runId', params.runId);
142
+ }
143
+
144
+ return this.request(`/api/workflows/v-next/${this.workflowId}/create-run?${searchParams.toString()}`, {
145
+ method: 'POST',
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Starts a vNext workflow run synchronously without waiting for the workflow to complete
151
+ * @param params - Object containing the runId, inputData and runtimeContext
152
+ * @returns Promise containing success message
153
+ */
154
+ start(params: {
155
+ runId: string;
156
+ inputData: Record<string, any>;
157
+ runtimeContext?: RuntimeContext;
158
+ }): Promise<{ message: string }> {
159
+ return this.request(`/api/workflows/v-next/${this.workflowId}/start?runId=${params.runId}`, {
160
+ method: 'POST',
161
+ body: { inputData: params?.inputData, runtimeContext: params.runtimeContext },
162
+ });
163
+ }
164
+
165
+ /**
166
+ * Resumes a suspended vNext workflow step synchronously without waiting for the vNext workflow to complete
167
+ * @param params - Object containing the runId, step, resumeData and runtimeContext
168
+ * @returns Promise containing success message
169
+ */
170
+ resume({
171
+ step,
172
+ runId,
173
+ resumeData,
174
+ runtimeContext,
175
+ }: {
176
+ step: string | string[];
177
+ runId: string;
178
+ resumeData?: Record<string, any>;
179
+ runtimeContext?: RuntimeContext;
180
+ }): Promise<{ message: string }> {
181
+ return this.request(`/api/workflows/v-next/${this.workflowId}/resume?runId=${runId}`, {
182
+ method: 'POST',
183
+ stream: true,
184
+ body: {
185
+ step,
186
+ resumeData,
187
+ runtimeContext,
188
+ },
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Starts a vNext workflow run asynchronously and returns a promise that resolves when the vNext workflow is complete
194
+ * @param params - Object containing the optional runId, inputData and runtimeContext
195
+ * @returns Promise containing the vNext workflow execution results
196
+ */
197
+ startAsync(params: {
198
+ runId?: string;
199
+ inputData: Record<string, any>;
200
+ runtimeContext?: RuntimeContext;
201
+ }): Promise<VNextWorkflowRunResult> {
202
+ const searchParams = new URLSearchParams();
203
+
204
+ if (!!params?.runId) {
205
+ searchParams.set('runId', params.runId);
206
+ }
207
+
208
+ return this.request(`/api/workflows/v-next/${this.workflowId}/start-async?${searchParams.toString()}`, {
209
+ method: 'POST',
210
+ body: { inputData: params.inputData, runtimeContext: params.runtimeContext },
211
+ });
212
+ }
213
+
214
+ /**
215
+ * Resumes a suspended vNext workflow step asynchronously and returns a promise that resolves when the vNext workflow is complete
216
+ * @param params - Object containing the runId, step, resumeData and runtimeContext
217
+ * @returns Promise containing the vNext workflow resume results
218
+ */
219
+ resumeAsync(params: {
220
+ runId: string;
221
+ step: string | string[];
222
+ resumeData?: Record<string, any>;
223
+ runtimeContext?: RuntimeContext;
224
+ }): Promise<VNextWorkflowRunResult> {
225
+ return this.request(`/api/workflows/v-next/${this.workflowId}/resume-async?runId=${params.runId}`, {
226
+ method: 'POST',
227
+ body: {
228
+ step: params.step,
229
+ resumeData: params.resumeData,
230
+ runtimeContext: params.runtimeContext,
231
+ },
232
+ });
233
+ }
234
+
235
+ /**
236
+ * Watches vNext workflow transitions in real-time
237
+ * @param runId - Optional run ID to filter the watch stream
238
+ * @returns AsyncGenerator that yields parsed records from the vNext workflow watch stream
239
+ */
240
+ async watch({ runId }: { runId?: string }, onRecord: (record: VNextWorkflowWatchResult) => void) {
241
+ const response: Response = await this.request(`/api/workflows/v-next/${this.workflowId}/watch?runId=${runId}`, {
242
+ stream: true,
243
+ });
244
+
245
+ if (!response.ok) {
246
+ throw new Error(`Failed to watch vNext workflow: ${response.statusText}`);
247
+ }
248
+
249
+ if (!response.body) {
250
+ throw new Error('Response body is null');
251
+ }
252
+
253
+ for await (const record of this.streamProcessor(response.body)) {
254
+ onRecord(record);
255
+ }
256
+ }
257
+ }