Skip to content

Commit 10523f4

Browse files
authored
Fix/12293 voice type (#12329)
## Description Fixed type error when passing MastraVoice implementations (like OpenAIVoice) directly to Agent's voice config. The `AgentConfig.voice` property was typed as `CompositeVoice` instead of `MastraVoice`, causing TypeScript errors when users tried to pass voice providers directly. Changed `voice?: CompositeVoice` to `voice?: MastraVoice` in both `types.ts` and `agent.ts`. ## Related Issue(s) Fixes #12293 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Code refactoring - [ ] Performance improvement - [x] Test update ## Checklist - [x] I have made corresponding changes to the documentation (if applicable) - [x] I have added tests that prove my fix is effective or that my feature works <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Agent voice configuration has been enhanced to accept any voice implementation directly. Previously, voices required wrapping; now you can pass implementations like OpenAIVoice without additional abstraction layers. * **Tests** * Added comprehensive type compatibility tests to validate voice implementation usage across different voice types. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent c9886f7 commit 10523f4

File tree

4 files changed

+85
-5
lines changed

4 files changed

+85
-5
lines changed

.changeset/perky-trains-grab.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@mastra/core': patch
3+
---
4+
5+
Fixed type error when passing MastraVoice implementations (like OpenAIVoice) directly to Agent's voice config. Previously, the voice property only accepted CompositeVoice, requiring users to wrap their voice provider. Now you can pass any MastraVoice implementation directly.
6+
7+
**Before (required wrapper):**
8+
9+
```typescript
10+
const agent = new Agent({
11+
voice: new CompositeVoice({ output: new OpenAIVoice() }),
12+
});
13+
```
14+
15+
**After (direct usage):**
16+
17+
```typescript
18+
const agent = new Agent({
19+
voice: new OpenAIVoice(),
20+
});
21+
```
22+
23+
Fixes #12293

packages/core/src/agent/__tests__/voice.test.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { PassThrough } from 'node:stream';
22
import { openai } from '@ai-sdk/openai-v5';
3-
import { beforeEach, describe, expect, it } from 'vitest';
3+
import { beforeEach, describe, expect, it, expectTypeOf } from 'vitest';
44
import { CompositeVoice } from '../../voice/composite-voice';
55
import { MastraVoice } from '../../voice/voice';
66
import { Agent } from '../agent';
7+
import type { AgentConfig } from '../types';
78

89
describe('voice capabilities', () => {
910
class MockVoice extends MastraVoice {
@@ -122,4 +123,60 @@ describe('voice capabilities', () => {
122123
await expect(agentWithoutVoice.voice.listen(new PassThrough())).rejects.toThrow('No voice provider configured');
123124
});
124125
});
126+
127+
/**
128+
* Type compatibility tests for GitHub Issue #12293
129+
* https://github.com/mastra-ai/mastra/issues/12293
130+
*
131+
* Verifies that MastraVoice implementations (like OpenAIVoice) can be
132+
* passed directly to Agent.voice without wrapping in CompositeVoice.
133+
*/
134+
describe('type compatibility (issue #12293)', () => {
135+
it('AgentConfig.voice should accept MastraVoice', () => {
136+
type VoiceConfigType = NonNullable<AgentConfig['voice']>;
137+
expectTypeOf<MastraVoice>().toMatchTypeOf<VoiceConfigType>();
138+
});
139+
140+
it('AgentConfig.voice should accept MastraVoice subclasses', () => {
141+
type VoiceConfigType = NonNullable<AgentConfig['voice']>;
142+
expectTypeOf<MockVoice>().toMatchTypeOf<VoiceConfigType>();
143+
});
144+
145+
it('AgentConfig.voice should accept CompositeVoice', () => {
146+
type VoiceConfigType = NonNullable<AgentConfig['voice']>;
147+
expectTypeOf<CompositeVoice>().toMatchTypeOf<VoiceConfigType>();
148+
});
149+
150+
it('should accept MastraVoice directly without CompositeVoice wrapper', () => {
151+
const mockVoice = new MockVoice({ speaker: 'mock-voice' });
152+
153+
const agent = new Agent({
154+
id: 'direct-voice-agent',
155+
name: 'Direct Voice Agent',
156+
instructions: 'You are a voice assistant.',
157+
model: openai('gpt-4o-mini'),
158+
voice: mockVoice,
159+
});
160+
161+
expect(agent.voice).toBeDefined();
162+
});
163+
164+
it('voice methods should work when MastraVoice passed directly', async () => {
165+
const mockVoice = new MockVoice({ speaker: 'mock-voice' });
166+
167+
const agent = new Agent({
168+
id: 'direct-voice-agent',
169+
name: 'Direct Voice Agent',
170+
instructions: 'You are a voice assistant.',
171+
model: openai('gpt-4o-mini'),
172+
voice: mockVoice,
173+
});
174+
175+
const speakers = await agent.voice.getSpeakers();
176+
expect(speakers).toEqual([{ voiceId: 'mock-voice' }]);
177+
178+
const audioStream = await agent.voice.speak('Hello');
179+
expect(audioStream).toBeDefined();
180+
});
181+
});
125182
});

packages/core/src/agent/agent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import type { CoreTool } from '../tools/types';
4545
import type { DynamicArgument } from '../types';
4646
import { makeCoreTool, createMastraProxy, ensureToolProperties, isZodType } from '../utils';
4747
import type { ToolOptions } from '../utils';
48-
import type { CompositeVoice } from '../voice';
48+
import type { MastraVoice } from '../voice';
4949
import { DefaultVoice } from '../voice';
5050
import { createWorkflow, createStep, isProcessor } from '../workflows';
5151
import type { OutputWriter, Step, Workflow, WorkflowResult } from '../workflows';
@@ -139,7 +139,7 @@ export class Agent<
139139
#tools: DynamicArgument<TTools>;
140140
#scorers: DynamicArgument<MastraScorers>;
141141
#agents: DynamicArgument<Record<string, Agent>>;
142-
#voice: CompositeVoice;
142+
#voice: MastraVoice;
143143
#inputProcessors?: DynamicArgument<InputProcessorOrWorkflow[]>;
144144
#outputProcessors?: DynamicArgument<OutputProcessorOrWorkflow[]>;
145145
#maxProcessorRetries?: number;

packages/core/src/agent/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import type { OutputSchema } from '../stream';
3232
import type { ModelManagerModelConfig } from '../stream/types';
3333
import type { ToolAction, VercelTool, VercelToolV5 } from '../tools';
3434
import type { DynamicArgument } from '../types';
35-
import type { CompositeVoice } from '../voice';
35+
import type { MastraVoice } from '../voice';
3636
import type { Workflow } from '../workflows';
3737
import type { Agent } from './agent';
3838
import type { AgentExecutionOptions, NetworkOptions } from './agent.types';
@@ -222,7 +222,7 @@ export interface AgentConfig<
222222
/**
223223
* Voice settings for speech input and output.
224224
*/
225-
voice?: CompositeVoice;
225+
voice?: MastraVoice;
226226
/**
227227
* Input processors that can modify or validate messages before they are processed by the agent.
228228
* These can be individual processors (implementing `processInput` or `processInputStep`) or

0 commit comments

Comments
 (0)