@mastra/client-js 0.0.0-storage-20250225005900 → 0.0.0-switch-to-core-20250424015131

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/src/index.test.ts CHANGED
@@ -12,33 +12,51 @@ describe('MastraClient Resources', () => {
12
12
  baseUrl: 'http://localhost:4111',
13
13
  headers: {
14
14
  Authorization: 'Bearer test-key',
15
+ 'x-mastra-client-type': 'js',
15
16
  },
16
17
  };
17
18
 
18
19
  // Helper to mock successful API responses
19
20
  const mockFetchResponse = (data: any, options: { isStream?: boolean } = {}) => {
20
21
  if (options.isStream) {
21
- const stream = new ReadableStream({
22
- start(controller) {
23
- controller.enqueue(new TextEncoder().encode(JSON.stringify(data)));
24
- controller.close();
25
- },
26
- });
27
- (global.fetch as any).mockResolvedValueOnce({
28
- ok: true,
29
- headers: {
30
- get: (name: string) => (name === 'Content-Type' ? 'text/event-stream' : null),
31
- },
32
- body: stream,
33
- });
22
+ let contentType = 'text/event-stream';
23
+ let responseBody: ReadableStream;
24
+
25
+ if (data instanceof ReadableStream) {
26
+ responseBody = data;
27
+ contentType = 'audio/mp3';
28
+ } else {
29
+ responseBody = new ReadableStream({
30
+ start(controller) {
31
+ controller.enqueue(new TextEncoder().encode(JSON.stringify(data)));
32
+ controller.close();
33
+ },
34
+ });
35
+ }
36
+
37
+ const headers = new Headers();
38
+ if (contentType === 'audio/mp3') {
39
+ headers.set('Transfer-Encoding', 'chunked');
40
+ }
41
+ headers.set('Content-Type', contentType);
42
+
43
+ (global.fetch as any).mockResolvedValueOnce(
44
+ new Response(responseBody, {
45
+ status: 200,
46
+ statusText: 'OK',
47
+ headers,
48
+ }),
49
+ );
34
50
  } else {
35
- (global.fetch as any).mockResolvedValueOnce({
36
- ok: true,
37
- headers: {
38
- get: (name: string) => (name === 'Content-Type' ? 'application/json' : null),
39
- },
40
- json: async () => data,
51
+ const response = new Response(undefined, {
52
+ status: 200,
53
+ statusText: 'OK',
54
+ headers: new Headers({
55
+ 'Content-Type': 'application/json',
56
+ }),
41
57
  });
58
+ response.json = () => Promise.resolve(data);
59
+ (global.fetch as any).mockResolvedValueOnce(response);
42
60
  }
43
61
  };
44
62
 
@@ -241,7 +259,7 @@ describe('MastraClient Resources', () => {
241
259
  const result = await agent.generate({
242
260
  messages: [],
243
261
  threadId: 'test-thread',
244
- resourceid: 'test-resource',
262
+ resourceId: 'test-resource',
245
263
  output: {},
246
264
  });
247
265
  expect(result).toEqual(mockResponse);
@@ -253,7 +271,7 @@ describe('MastraClient Resources', () => {
253
271
  body: JSON.stringify({
254
272
  messages: [],
255
273
  threadId: 'test-thread',
256
- resourceid: 'test-resource',
274
+ resourceId: 'test-resource',
257
275
  output: {},
258
276
  }),
259
277
  }),
@@ -301,19 +319,18 @@ describe('MastraClient Resources', () => {
301
319
  });
302
320
 
303
321
  it('should get agent evals', async () => {
304
- const mockResponse = {
305
- name: 'Test Agent',
306
- evals: [{ id: 'eval1' }],
307
- };
322
+ const mockResponse = { data: 'test' };
308
323
  mockFetchResponse(mockResponse);
309
324
  const result = await agent.evals();
310
325
  expect(result).toEqual(mockResponse);
311
- expect(global.fetch).toHaveBeenCalledWith(`${clientOptions.baseUrl}/api/agents/test-agent/evals/ci`, {
312
- headers: {
313
- Authorization: 'Bearer test-key',
314
- 'Content-Type': 'application/json',
315
- },
316
- });
326
+ expect(global.fetch).toHaveBeenCalledWith(
327
+ `${clientOptions.baseUrl}/api/agents/test-agent/evals/ci`,
328
+ expect.objectContaining({
329
+ headers: expect.objectContaining({
330
+ Authorization: 'Bearer test-key',
331
+ }),
332
+ }),
333
+ );
317
334
  });
318
335
 
319
336
  it('should get live evals', async () => {
@@ -333,6 +350,106 @@ describe('MastraClient Resources', () => {
333
350
  });
334
351
  });
335
352
 
353
+ describe('Agent Voice Resource', () => {
354
+ const agentId = 'test-agent';
355
+ let agent: ReturnType<typeof client.getAgent>;
356
+ beforeEach(() => {
357
+ agent = client.getAgent(agentId);
358
+ });
359
+ it('should get available speakers', async () => {
360
+ const mockResponse = [{ voiceId: 'speaker1' }];
361
+ mockFetchResponse(mockResponse);
362
+
363
+ const result = await agent.voice.getSpeakers();
364
+
365
+ expect(result).toEqual(mockResponse);
366
+ expect(global.fetch).toHaveBeenCalledWith(
367
+ `${clientOptions.baseUrl}/api/agents/test-agent/voice/speakers`,
368
+ expect.objectContaining({
369
+ headers: expect.objectContaining(clientOptions.headers),
370
+ }),
371
+ );
372
+ });
373
+
374
+ it(`should call speak without options`, async () => {
375
+ const mockAudioStream = new ReadableStream();
376
+ mockFetchResponse(mockAudioStream, { isStream: true });
377
+
378
+ const result = await agent.voice.speak('test');
379
+
380
+ expect(result).toBeInstanceOf(Response);
381
+ expect(result.body).toBeInstanceOf(ReadableStream);
382
+ expect(global.fetch).toHaveBeenCalledWith(
383
+ `${clientOptions.baseUrl}/api/agents/test-agent/voice/speak`,
384
+ expect.objectContaining({
385
+ method: 'POST',
386
+ headers: expect.objectContaining(clientOptions.headers),
387
+ }),
388
+ );
389
+ });
390
+
391
+ it(`should call speak with options`, async () => {
392
+ const mockAudioStream = new ReadableStream();
393
+ mockFetchResponse(mockAudioStream, { isStream: true });
394
+
395
+ const result = await agent.voice.speak('test', { speaker: 'speaker1' });
396
+ expect(result).toBeInstanceOf(Response);
397
+ expect(result.body).toBeInstanceOf(ReadableStream);
398
+ expect(global.fetch).toHaveBeenCalledWith(
399
+ `${clientOptions.baseUrl}/api/agents/test-agent/voice/speak`,
400
+ expect.objectContaining({
401
+ method: 'POST',
402
+ headers: expect.objectContaining(clientOptions.headers),
403
+ }),
404
+ );
405
+ });
406
+
407
+ it(`should call listen with audio file`, async () => {
408
+ const transcriptionResponse = { text: 'Hello world' };
409
+ mockFetchResponse(transcriptionResponse);
410
+
411
+ const audioBlob = new Blob(['test audio data'], { type: 'audio/wav' });
412
+
413
+ const result = await agent.voice.listen(audioBlob, { filetype: 'wav' });
414
+ expect(result).toEqual(transcriptionResponse);
415
+
416
+ expect(global.fetch).toHaveBeenCalledTimes(1);
417
+ const [url, config] = (global.fetch as any).mock.calls[0];
418
+ expect(url).toBe(`${clientOptions.baseUrl}/api/agents/test-agent/voice/listen`);
419
+ expect(config.method).toBe('POST');
420
+ expect(config.headers).toMatchObject(clientOptions.headers);
421
+
422
+ const formData = config.body;
423
+ expect(formData).toBeInstanceOf(FormData);
424
+ const audioContent = formData.get('audio');
425
+ expect(audioContent).toBeInstanceOf(Blob);
426
+ expect(audioContent.type).toBe('audio/wav');
427
+ });
428
+
429
+ it(`should call listen with audio blob and options`, async () => {
430
+ const transcriptionResponse = { text: 'Hello world' };
431
+ mockFetchResponse(transcriptionResponse);
432
+
433
+ const audioBlob = new Blob(['test audio data'], { type: 'audio/mp3' });
434
+
435
+ const result = await agent.voice.listen(audioBlob, { filetype: 'mp3' });
436
+
437
+ expect(result).toEqual(transcriptionResponse);
438
+
439
+ expect(global.fetch).toHaveBeenCalledTimes(1);
440
+ const [url, config] = (global.fetch as any).mock.calls[0];
441
+ expect(url).toBe(`${clientOptions.baseUrl}/api/agents/test-agent/voice/listen`);
442
+ expect(config.method).toBe('POST');
443
+ expect(config.headers).toMatchObject(clientOptions.headers);
444
+
445
+ const formData = config.body as FormData;
446
+ expect(formData).toBeInstanceOf(FormData);
447
+ const audioContent = formData.get('audio');
448
+ expect(audioContent).toBeInstanceOf(Blob);
449
+ expect(formData.get('options')).toBe(JSON.stringify({ filetype: 'mp3' }));
450
+ });
451
+ });
452
+
336
453
  const agentId = 'test-agent';
337
454
 
338
455
  describe('Memory Thread Resource', () => {
@@ -412,29 +529,27 @@ describe('MastraClient Resources', () => {
412
529
  });
413
530
 
414
531
  it('should save messages to memory', async () => {
415
- const messages: MessageType[] = [
532
+ const messages = [
416
533
  {
417
534
  id: '1',
418
- type: 'text',
535
+ type: 'text' as const,
419
536
  content: 'test',
420
- role: 'user',
537
+ role: 'user' as const,
421
538
  threadId: 'test-thread',
422
- createdAt: new Date(),
539
+ createdAt: new Date('2025-03-26T10:40:55.116Z'),
423
540
  },
424
541
  ];
425
542
  mockFetchResponse(messages);
426
- const result = await client.saveMessageToMemory({ messages, agentId });
543
+ const result = await client.saveMessageToMemory({ agentId, messages });
427
544
  expect(result).toEqual(messages);
428
545
  expect(global.fetch).toHaveBeenCalledWith(
429
546
  `${clientOptions.baseUrl}/api/memory/save-messages?agentId=${agentId}`,
430
- {
547
+ expect.objectContaining({
431
548
  method: 'POST',
432
- headers: {
549
+ headers: expect.objectContaining({
433
550
  Authorization: 'Bearer test-key',
434
- 'Content-Type': 'application/json',
435
- },
436
- body: JSON.stringify({ messages, agentId }),
437
- },
551
+ }),
552
+ }),
438
553
  );
439
554
  });
440
555
  });
@@ -467,21 +582,19 @@ describe('MastraClient Resources', () => {
467
582
  });
468
583
 
469
584
  it('should execute tool', async () => {
470
- const mockResponse = {
471
- result: 'Tool execution result',
472
- };
585
+ const mockResponse = { data: 'test' };
473
586
  mockFetchResponse(mockResponse);
474
-
475
587
  const result = await tool.execute({ data: '' });
476
588
  expect(result).toEqual(mockResponse);
477
- expect(global.fetch).toHaveBeenCalledWith(`${clientOptions.baseUrl}/api/tools/test-tool/execute`, {
478
- method: 'POST',
479
- headers: {
480
- Authorization: 'Bearer test-key',
481
- 'Content-Type': 'application/json',
482
- },
483
- body: JSON.stringify({ data: '' }),
484
- });
589
+ expect(global.fetch).toHaveBeenCalledWith(
590
+ `${clientOptions.baseUrl}/api/tools/test-tool/execute`,
591
+ expect.objectContaining({
592
+ method: 'POST',
593
+ headers: expect.objectContaining({
594
+ Authorization: 'Bearer test-key',
595
+ }),
596
+ }),
597
+ );
485
598
  });
486
599
  });
487
600
 
@@ -519,14 +632,14 @@ describe('MastraClient Resources', () => {
519
632
  };
520
633
  mockFetchResponse(mockResponse);
521
634
 
522
- const result = await workflow.execute({ trigger: 'test' });
635
+ const result = await workflow.startAsync({ triggerData: { test: 'test' } });
523
636
  expect(result).toEqual(mockResponse);
524
637
  expect(global.fetch).toHaveBeenCalledWith(
525
- `${clientOptions.baseUrl}/api/workflows/test-workflow/execute`,
638
+ `${clientOptions.baseUrl}/api/workflows/test-workflow/start-async?`,
526
639
  expect.objectContaining({
527
640
  method: 'POST',
528
641
  headers: expect.objectContaining(clientOptions.headers),
529
- body: JSON.stringify({ trigger: 'test' }),
642
+ body: JSON.stringify({ test: 'test' }),
530
643
  }),
531
644
  );
532
645
  });
@@ -1,7 +1,8 @@
1
- import type { GenerateReturn, StreamReturn } from '@mastra/core';
1
+ import type { GenerateReturn } from '@mastra/core';
2
2
  import type { JSONSchema7 } from 'json-schema';
3
3
  import { ZodSchema } from 'zod';
4
4
  import { zodToJsonSchema } from 'zod-to-json-schema';
5
+ import { processDataStream } from '@ai-sdk/ui-utils';
5
6
 
6
7
  import type {
7
8
  GenerateParams,
@@ -28,6 +29,7 @@ export class AgentTool extends BaseResource {
28
29
  * @param params - Parameters required for tool execution
29
30
  * @returns Promise containing tool execution results
30
31
  */
32
+ /** @deprecated use CreateRun/startRun */
31
33
  execute(params: { data: any }): Promise<any> {
32
34
  return this.request(`/api/agents/${this.agentId}/tools/${this.toolId}/execute`, {
33
35
  method: 'POST',
@@ -36,12 +38,70 @@ export class AgentTool extends BaseResource {
36
38
  }
37
39
  }
38
40
 
41
+ export class AgentVoice extends BaseResource {
42
+ constructor(
43
+ options: ClientOptions,
44
+ private agentId: string,
45
+ ) {
46
+ super(options);
47
+ this.agentId = agentId;
48
+ }
49
+
50
+ /**
51
+ * Convert text to speech using the agent's voice provider
52
+ * @param text - Text to convert to speech
53
+ * @param options - Optional provider-specific options for speech generation
54
+ * @returns Promise containing the audio data
55
+ */
56
+ async speak(text: string, options?: { speaker?: string; [key: string]: any }): Promise<Response> {
57
+ return this.request<Response>(`/api/agents/${this.agentId}/voice/speak`, {
58
+ method: 'POST',
59
+ headers: {
60
+ 'Content-Type': 'application/json',
61
+ },
62
+ body: { input: text, options },
63
+ stream: true,
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Convert speech to text using the agent's voice provider
69
+ * @param audio - Audio data to transcribe
70
+ * @param options - Optional provider-specific options
71
+ * @returns Promise containing the transcribed text
72
+ */
73
+ listen(audio: Blob, options?: Record<string, any>): Promise<Response> {
74
+ const formData = new FormData();
75
+ formData.append('audio', audio);
76
+
77
+ if (options) {
78
+ formData.append('options', JSON.stringify(options));
79
+ }
80
+
81
+ return this.request(`/api/agents/${this.agentId}/voice/listen`, {
82
+ method: 'POST',
83
+ body: formData,
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Get available speakers for the agent's voice provider
89
+ * @returns Promise containing list of available speakers
90
+ */
91
+ getSpeakers(): Promise<Array<{ voiceId: string; [key: string]: any }>> {
92
+ return this.request(`/api/agents/${this.agentId}/voice/speakers`);
93
+ }
94
+ }
95
+
39
96
  export class Agent extends BaseResource {
97
+ public readonly voice: AgentVoice;
98
+
40
99
  constructor(
41
100
  options: ClientOptions,
42
101
  private agentId: string,
43
102
  ) {
44
103
  super(options);
104
+ this.voice = new AgentVoice(options, this.agentId);
45
105
  }
46
106
 
47
107
  /**
@@ -63,6 +123,10 @@ export class Agent extends BaseResource {
63
123
  const processedParams = {
64
124
  ...params,
65
125
  output: params.output instanceof ZodSchema ? zodToJsonSchema(params.output) : params.output,
126
+ experimental_output:
127
+ params.experimental_output instanceof ZodSchema
128
+ ? zodToJsonSchema(params.experimental_output)
129
+ : params.experimental_output,
66
130
  };
67
131
 
68
132
  return this.request(`/api/agents/${this.agentId}/generate`, {
@@ -74,19 +138,44 @@ export class Agent extends BaseResource {
74
138
  /**
75
139
  * Streams a response from the agent
76
140
  * @param params - Stream parameters including prompt
77
- * @returns Promise containing the streamed response
141
+ * @returns Promise containing the enhanced Response object with processDataStream method
78
142
  */
79
- stream<T extends JSONSchema7 | ZodSchema | undefined = undefined>(params: StreamParams<T>): Promise<Response> {
143
+ async stream<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
144
+ params: StreamParams<T>,
145
+ ): Promise<
146
+ Response & {
147
+ processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
148
+ }
149
+ > {
80
150
  const processedParams = {
81
151
  ...params,
82
152
  output: params.output instanceof ZodSchema ? zodToJsonSchema(params.output) : params.output,
153
+ experimental_output:
154
+ params.experimental_output instanceof ZodSchema
155
+ ? zodToJsonSchema(params.experimental_output)
156
+ : params.experimental_output,
83
157
  };
84
158
 
85
- return this.request(`/api/agents/${this.agentId}/stream`, {
159
+ const response: Response & {
160
+ processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
161
+ } = await this.request(`/api/agents/${this.agentId}/stream`, {
86
162
  method: 'POST',
87
163
  body: processedParams,
88
164
  stream: true,
89
165
  });
166
+
167
+ if (!response.body) {
168
+ throw new Error('No response body');
169
+ }
170
+
171
+ response.processDataStream = async (options = {}) => {
172
+ await processDataStream({
173
+ stream: response.body as ReadableStream<Uint8Array>,
174
+ ...options,
175
+ });
176
+ };
177
+
178
+ return response;
90
179
  }
91
180
 
92
181
  /**
@@ -24,11 +24,13 @@ export class BaseResource {
24
24
  const response = await fetch(`${baseUrl}${path}`, {
25
25
  ...options,
26
26
  headers: {
27
- 'Content-Type': 'application/json',
28
27
  ...headers,
29
28
  ...options.headers,
29
+ // TODO: Bring this back once we figure out what we/users need to do to make this work with cross-origin requests
30
+ // 'x-mastra-client-type': 'js',
30
31
  },
31
- body: options.body ? JSON.stringify(options.body) : undefined,
32
+ body:
33
+ options.body instanceof FormData ? options.body : options.body ? JSON.stringify(options.body) : undefined,
32
34
  });
33
35
 
34
36
  if (!response.ok) {
@@ -1,4 +1,5 @@
1
1
  export * from './agent';
2
+ export * from './network';
2
3
  export * from './memory-thread';
3
4
  export * from './vector';
4
5
  export * from './workflow';
@@ -1,13 +1,6 @@
1
1
  import type { StorageThreadType } from '@mastra/core';
2
2
 
3
- import type {
4
- CreateMemoryThreadParams,
5
- GetMemoryThreadMessagesResponse,
6
- GetMemoryThreadResponse,
7
- ClientOptions,
8
- SaveMessageToMemoryParams,
9
- UpdateMemoryThreadParams,
10
- } from '../types';
3
+ import type { GetMemoryThreadMessagesResponse, ClientOptions, UpdateMemoryThreadParams } from '../types';
11
4
 
12
5
  import { BaseResource } from './base';
13
6
 
@@ -0,0 +1,92 @@
1
+ import type { GenerateReturn } from '@mastra/core';
2
+ import type { JSONSchema7 } from 'json-schema';
3
+ import { ZodSchema } from 'zod';
4
+ import { zodToJsonSchema } from 'zod-to-json-schema';
5
+
6
+ import type { GenerateParams, ClientOptions, StreamParams, GetNetworkResponse } from '../types';
7
+
8
+ import { BaseResource } from './base';
9
+ import { processDataStream } from '@ai-sdk/ui-utils';
10
+
11
+ export class Network extends BaseResource {
12
+ constructor(
13
+ options: ClientOptions,
14
+ private networkId: string,
15
+ ) {
16
+ super(options);
17
+ }
18
+
19
+ /**
20
+ * Retrieves details about the network
21
+ * @returns Promise containing network details
22
+ */
23
+ details(): Promise<GetNetworkResponse> {
24
+ return this.request(`/api/networks/${this.networkId}`);
25
+ }
26
+
27
+ /**
28
+ * Generates a response from the agent
29
+ * @param params - Generation parameters including prompt
30
+ * @returns Promise containing the generated response
31
+ */
32
+ generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
33
+ params: GenerateParams<T>,
34
+ ): Promise<GenerateReturn<T>> {
35
+ const processedParams = {
36
+ ...params,
37
+ output: params.output instanceof ZodSchema ? zodToJsonSchema(params.output) : params.output,
38
+ experimental_output:
39
+ params.experimental_output instanceof ZodSchema
40
+ ? zodToJsonSchema(params.experimental_output)
41
+ : params.experimental_output,
42
+ };
43
+
44
+ return this.request(`/api/networks/${this.networkId}/generate`, {
45
+ method: 'POST',
46
+ body: processedParams,
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Streams a response from the agent
52
+ * @param params - Stream parameters including prompt
53
+ * @returns Promise containing the enhanced Response object with processDataStream method
54
+ */
55
+ async stream<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
56
+ params: StreamParams<T>,
57
+ ): Promise<
58
+ Response & {
59
+ processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
60
+ }
61
+ > {
62
+ const processedParams = {
63
+ ...params,
64
+ output: params.output instanceof ZodSchema ? zodToJsonSchema(params.output) : params.output,
65
+ experimental_output:
66
+ params.experimental_output instanceof ZodSchema
67
+ ? zodToJsonSchema(params.experimental_output)
68
+ : params.experimental_output,
69
+ };
70
+
71
+ const response: Response & {
72
+ processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
73
+ } = await this.request(`/api/networks/${this.networkId}/stream`, {
74
+ method: 'POST',
75
+ body: processedParams,
76
+ stream: true,
77
+ });
78
+
79
+ if (!response.body) {
80
+ throw new Error('No response body');
81
+ }
82
+
83
+ response.processDataStream = async (options = {}) => {
84
+ await processDataStream({
85
+ stream: response.body as ReadableStream<Uint8Array>,
86
+ ...options,
87
+ });
88
+ };
89
+
90
+ return response;
91
+ }
92
+ }