@mingxy/ocosay 1.0.3 → 1.0.5
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/README.md +12 -0
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/plugin.d.ts +2 -4
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +6 -4
- package/dist/plugin.js.map +2 -2
- package/package.json +1 -1
- package/TECH_PLAN.md +0 -352
- package/__mocks__/@opencode-ai/plugin.ts +0 -32
- package/jest.config.js +0 -15
- package/src/config.ts +0 -183
- package/src/core/backends/afplay-backend.ts +0 -162
- package/src/core/backends/aplay-backend.ts +0 -160
- package/src/core/backends/base.ts +0 -117
- package/src/core/backends/index.ts +0 -128
- package/src/core/backends/naudiodon-backend.ts +0 -164
- package/src/core/backends/powershell-backend.ts +0 -173
- package/src/core/player.ts +0 -322
- package/src/core/speaker.ts +0 -283
- package/src/core/stream-player.ts +0 -326
- package/src/core/stream-reader.ts +0 -190
- package/src/core/streaming-synthesizer.ts +0 -123
- package/src/core/types.ts +0 -185
- package/src/index.ts +0 -236
- package/src/plugin.ts +0 -178
- package/src/providers/base.ts +0 -150
- package/src/providers/minimax.ts +0 -515
- package/src/tools/tts.ts +0 -277
- package/src/types/config.ts +0 -38
- package/src/types/naudiodon.d.ts +0 -19
- package/tests/__mocks__/@opencode-ai/plugin.ts +0 -32
- package/tests/backends.test.ts +0 -831
- package/tests/config.test.ts +0 -327
- package/tests/index.test.ts +0 -201
- package/tests/integration-test.d.ts +0 -6
- package/tests/integration-test.d.ts.map +0 -1
- package/tests/integration-test.js +0 -84
- package/tests/integration-test.js.map +0 -1
- package/tests/integration-test.ts +0 -93
- package/tests/p1-fixes.test.ts +0 -160
- package/tests/plugin.test.ts +0 -312
- package/tests/provider.test.d.ts +0 -2
- package/tests/provider.test.d.ts.map +0 -1
- package/tests/provider.test.js +0 -69
- package/tests/provider.test.js.map +0 -1
- package/tests/provider.test.ts +0 -87
- package/tests/speaker.test.d.ts +0 -2
- package/tests/speaker.test.d.ts.map +0 -1
- package/tests/speaker.test.js +0 -63
- package/tests/speaker.test.js.map +0 -1
- package/tests/speaker.test.ts +0 -232
- package/tests/stream-player.test.ts +0 -303
- package/tests/stream-reader.test.ts +0 -269
- package/tests/streaming-synthesizer.test.ts +0 -225
- package/tests/tts-tools.test.ts +0 -270
- package/tests/types.test.d.ts +0 -2
- package/tests/types.test.d.ts.map +0 -1
- package/tests/types.test.js +0 -61
- package/tests/types.test.js.map +0 -1
- package/tests/types.test.ts +0 -63
- package/tsconfig.json +0 -22
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { initialize, speak, stop, listVoices, getSpeaker } from '../src/index'
|
|
2
|
-
import { getDefaultSpeaker } from '../src/core/speaker'
|
|
3
|
-
import { getProvider } from '../src/providers/base'
|
|
4
|
-
|
|
5
|
-
const API_KEY = process.env.MINIMAX_API_KEY
|
|
6
|
-
|
|
7
|
-
if (!API_KEY) {
|
|
8
|
-
console.error('❌ 请设置环境变量 MINIMAX_API_KEY')
|
|
9
|
-
console.log('示例: MINIMAX_API_KEY=your-api-key node tests/integration-test.ts')
|
|
10
|
-
process.exit(1)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async function test() {
|
|
14
|
-
console.log('🔧 初始化 ocosay...')
|
|
15
|
-
|
|
16
|
-
await initialize({
|
|
17
|
-
providers: {
|
|
18
|
-
minimax: {
|
|
19
|
-
apiKey: API_KEY,
|
|
20
|
-
voiceId: 'male-qn-qingse',
|
|
21
|
-
model: 'stream'
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
console.log('✅ 初始化成功!\n')
|
|
27
|
-
|
|
28
|
-
console.log('📝 测试 1: 同步合成 (model: sync)')
|
|
29
|
-
try {
|
|
30
|
-
const provider = getProvider('minimax')
|
|
31
|
-
const result = await provider.speak('你好,这是同步合成测试!', { model: 'sync' })
|
|
32
|
-
console.log(`✅ 同步合成成功! 音频大小: ${result.audioData.length} bytes, 格式: ${result.format}`)
|
|
33
|
-
} catch (error: any) {
|
|
34
|
-
console.error('❌ 同步合成失败:', error.message || error)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
console.log('')
|
|
38
|
-
|
|
39
|
-
console.log('📝 测试 2: 流式合成 (model: stream)')
|
|
40
|
-
try {
|
|
41
|
-
const provider = getProvider('minimax')
|
|
42
|
-
const result = await provider.speak('你好,这是流式合成测试!', { model: 'stream' })
|
|
43
|
-
console.log(`✅ 流式合成成功! 音频大小: ${result.audioData.length} bytes, 格式: ${result.format}`)
|
|
44
|
-
} catch (error: any) {
|
|
45
|
-
console.error('❌ 流式合成失败:', error.message || error)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
console.log('')
|
|
49
|
-
|
|
50
|
-
console.log('📝 测试 3: 异步合成 (model: async)')
|
|
51
|
-
try {
|
|
52
|
-
const provider = getProvider('minimax')
|
|
53
|
-
const result = await provider.speak('你好,这是异步合成测试!', { model: 'async' })
|
|
54
|
-
console.log(`✅ 异步合成成功! 音频大小: ${result.audioData.length} bytes, 格式: ${result.format}`)
|
|
55
|
-
} catch (error: any) {
|
|
56
|
-
console.error('❌ 异步合成失败:', error.message || error)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
console.log('')
|
|
60
|
-
|
|
61
|
-
console.log('📝 测试 4: 列出音色')
|
|
62
|
-
try {
|
|
63
|
-
const voices = await listVoices('minimax')
|
|
64
|
-
console.log(`✅ 获取到 ${voices.length} 个音色:`)
|
|
65
|
-
voices.slice(0, 5).forEach((v: any) => {
|
|
66
|
-
console.log(` - ${v.id}: ${v.name} (${v.language || 'unknown'})`)
|
|
67
|
-
})
|
|
68
|
-
} catch (error: any) {
|
|
69
|
-
console.error('❌ 列出音色失败:', error.message || error)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
console.log('')
|
|
73
|
-
|
|
74
|
-
console.log('📝 测试 5: 便捷函数 speak 调用链验证')
|
|
75
|
-
try {
|
|
76
|
-
const speaker = getDefaultSpeaker()
|
|
77
|
-
speaker.on('end', () => console.log(' 播放结束!'))
|
|
78
|
-
speaker.on('error', (err: any) => console.log(' 播放错误(正常,没有音频设备):', err.message))
|
|
79
|
-
console.log('✅ speak 便捷函数调用链验证通过')
|
|
80
|
-
} catch (error: any) {
|
|
81
|
-
console.error('❌ speak 失败:', error.message || error)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
console.log('')
|
|
85
|
-
console.log('🎉 集成测试完成!')
|
|
86
|
-
|
|
87
|
-
process.exit(0)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
test().catch((err: any) => {
|
|
91
|
-
console.error('❌ 测试失败:', err)
|
|
92
|
-
process.exit(1)
|
|
93
|
-
})
|
package/tests/p1-fixes.test.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { Speaker } from '../src/core/speaker'
|
|
2
|
-
import { TTSCapabilities } from '../src/core/types'
|
|
3
|
-
import { registerProvider, unregisterProvider, BaseTTSProvider } from '../src/providers/base'
|
|
4
|
-
import { initialize, destroy, isAutoReadEnabled, getStreamReader } from '../src/index'
|
|
5
|
-
|
|
6
|
-
class MockProvider extends BaseTTSProvider {
|
|
7
|
-
name = 'mock'
|
|
8
|
-
capabilities: TTSCapabilities = { speak: true, stream: true }
|
|
9
|
-
private shouldThrow = false
|
|
10
|
-
private destroyCalled = false
|
|
11
|
-
|
|
12
|
-
protected async doSpeak(text: string, voice: string | undefined, model: any) {
|
|
13
|
-
if (this.shouldThrow) {
|
|
14
|
-
throw new Error('Mock synthesize error')
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
audioData: Buffer.from([1, 2, 3]),
|
|
18
|
-
format: 'mp3',
|
|
19
|
-
isStream: model === 'stream',
|
|
20
|
-
duration: 1.0
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async destroy(): Promise<void> {
|
|
25
|
-
this.destroyCalled = true
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
setShouldThrow(throwError: boolean): void {
|
|
29
|
-
this.shouldThrow = throwError
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
wasDestroyCalled(): boolean {
|
|
33
|
-
return this.destroyCalled
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
reset(): void {
|
|
37
|
-
this.shouldThrow = false
|
|
38
|
-
this.destroyCalled = false
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
describe('P1 Fixes', () => {
|
|
43
|
-
let mockProvider: MockProvider
|
|
44
|
-
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
mockProvider = new MockProvider()
|
|
47
|
-
registerProvider('mock', mockProvider)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
afterEach(async () => {
|
|
51
|
-
try {
|
|
52
|
-
await destroy()
|
|
53
|
-
} catch (e) {
|
|
54
|
-
// ignore destroy errors in cleanup
|
|
55
|
-
}
|
|
56
|
-
unregisterProvider('mock')
|
|
57
|
-
mockProvider.reset()
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
describe('P1-3: Speaker.destroy() 方法', () => {
|
|
61
|
-
it('should cleanup player when destroy() is called', async () => {
|
|
62
|
-
const speaker = new Speaker({ defaultProvider: 'mock' })
|
|
63
|
-
|
|
64
|
-
await speaker.destroy()
|
|
65
|
-
|
|
66
|
-
expect((speaker as any).player).toBeUndefined()
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('should reset isSpeaking and isPaused when destroy() is called', async () => {
|
|
70
|
-
const speaker = new Speaker({ defaultProvider: 'mock' })
|
|
71
|
-
|
|
72
|
-
expect(speaker.isPlaying()).toBe(false)
|
|
73
|
-
expect(speaker.isPausedState()).toBe(false)
|
|
74
|
-
|
|
75
|
-
await speaker.destroy()
|
|
76
|
-
|
|
77
|
-
expect(speaker.isPlaying()).toBe(false)
|
|
78
|
-
expect(speaker.isPausedState()).toBe(false)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('should cleanup currentProvider and currentText', async () => {
|
|
82
|
-
const speaker = new Speaker({ defaultProvider: 'mock' })
|
|
83
|
-
|
|
84
|
-
await speaker.destroy()
|
|
85
|
-
|
|
86
|
-
expect((speaker as any).currentProvider).toBeUndefined()
|
|
87
|
-
expect((speaker as any).currentText).toBeUndefined()
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
describe('P1-4: destroy() 调用 provider.destroy()', () => {
|
|
92
|
-
it('should call provider.destroy() when global destroy() is called', async () => {
|
|
93
|
-
await initialize({
|
|
94
|
-
defaultProvider: 'mock',
|
|
95
|
-
autoRead: false
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
expect(mockProvider.wasDestroyCalled()).toBe(false)
|
|
99
|
-
|
|
100
|
-
await destroy()
|
|
101
|
-
|
|
102
|
-
expect(mockProvider.wasDestroyCalled()).toBe(true)
|
|
103
|
-
})
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
describe('P1-1: destroy() 重置 autoReadEnabled', () => {
|
|
107
|
-
it('should reset autoReadEnabled to false after destroy()', async () => {
|
|
108
|
-
expect(isAutoReadEnabled()).toBe(false)
|
|
109
|
-
|
|
110
|
-
await initialize({
|
|
111
|
-
defaultProvider: 'mock',
|
|
112
|
-
autoRead: true
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
expect(isAutoReadEnabled()).toBe(true)
|
|
116
|
-
|
|
117
|
-
await destroy()
|
|
118
|
-
|
|
119
|
-
expect(isAutoReadEnabled()).toBe(false)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('should correctly check autoReadEnabled state', async () => {
|
|
123
|
-
await initialize({
|
|
124
|
-
defaultProvider: 'mock',
|
|
125
|
-
autoRead: false
|
|
126
|
-
})
|
|
127
|
-
expect(isAutoReadEnabled()).toBe(false)
|
|
128
|
-
await destroy()
|
|
129
|
-
|
|
130
|
-
await initialize({
|
|
131
|
-
defaultProvider: 'mock',
|
|
132
|
-
autoRead: true
|
|
133
|
-
})
|
|
134
|
-
expect(isAutoReadEnabled()).toBe(true)
|
|
135
|
-
await destroy()
|
|
136
|
-
expect(isAutoReadEnabled()).toBe(false)
|
|
137
|
-
})
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
describe('P1-2: processQueue() 错误处理', () => {
|
|
141
|
-
it('should not throw when provider synthesize throws', async () => {
|
|
142
|
-
await initialize({
|
|
143
|
-
defaultProvider: 'mock',
|
|
144
|
-
autoRead: true
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
const streamReader = getStreamReader()
|
|
148
|
-
expect(streamReader).toBeDefined()
|
|
149
|
-
|
|
150
|
-
mockProvider.setShouldThrow(true)
|
|
151
|
-
|
|
152
|
-
streamReader!.handleDelta('session1', 'msg1', 'part1', 'Error sentence')
|
|
153
|
-
streamReader!.handleEnd()
|
|
154
|
-
|
|
155
|
-
await new Promise(resolve => setTimeout(resolve, 50))
|
|
156
|
-
|
|
157
|
-
expect(() => streamReader!.handleDelta('session2', 'msg2', 'part2', 'Normal sentence')).not.toThrow()
|
|
158
|
-
})
|
|
159
|
-
})
|
|
160
|
-
})
|
package/tests/plugin.test.ts
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
jest.mock('@opencode-ai/plugin', () => {
|
|
2
|
-
const createChainable = () => {
|
|
3
|
-
const chain: any = {
|
|
4
|
-
describe: (desc: string) => {
|
|
5
|
-
chain._description = desc
|
|
6
|
-
return chain
|
|
7
|
-
},
|
|
8
|
-
optional: () => createChainable()
|
|
9
|
-
}
|
|
10
|
-
return chain
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const schema = {
|
|
14
|
-
string: () => createChainable(),
|
|
15
|
-
number: () => createChainable(),
|
|
16
|
-
enum: (values: string[]) => createChainable()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const tool = (input: any) => input
|
|
20
|
-
tool.schema = schema
|
|
21
|
-
|
|
22
|
-
return { tool, Plugin: jest.fn() }
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
jest.mock('../src/index', () => ({
|
|
26
|
-
handleToolCall: jest.fn(),
|
|
27
|
-
initialize: jest.fn(),
|
|
28
|
-
destroy: jest.fn()
|
|
29
|
-
}))
|
|
30
|
-
|
|
31
|
-
import { handleToolCall } from '../src/index'
|
|
32
|
-
import pluginExport from '../src/plugin'
|
|
33
|
-
const OcosayPlugin = pluginExport.server
|
|
34
|
-
|
|
35
|
-
describe('plugin.ts', () => {
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
jest.clearAllMocks()
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
describe('工具定义验证', () => {
|
|
41
|
-
let plugin: any
|
|
42
|
-
|
|
43
|
-
beforeEach(async () => {
|
|
44
|
-
plugin = await OcosayPlugin({} as any, {})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('tts_speak: name, description, input_schema 正确', () => {
|
|
48
|
-
const tool = plugin.tool.tts_speak
|
|
49
|
-
expect(tool.description).toBe('将文本转换为语音并播放')
|
|
50
|
-
expect(tool.args).toBeDefined()
|
|
51
|
-
expect(typeof tool.args).toBe('object')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('tts_stop: name, description, input_schema 正确', () => {
|
|
55
|
-
const tool = plugin.tool.tts_stop
|
|
56
|
-
expect(tool.description).toBe('停止当前 TTS 播放')
|
|
57
|
-
expect(tool.args).toBeDefined()
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('tts_pause: name, description, input_schema 正确', () => {
|
|
61
|
-
const tool = plugin.tool.tts_pause
|
|
62
|
-
expect(tool.description).toBe('暂停当前 TTS 播放')
|
|
63
|
-
expect(tool.args).toBeDefined()
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('tts_resume: name, description, input_schema 正确', () => {
|
|
67
|
-
const tool = plugin.tool.tts_resume
|
|
68
|
-
expect(tool.description).toBe('恢复暂停的 TTS 播放')
|
|
69
|
-
expect(tool.args).toBeDefined()
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('tts_list_voices: name, description, input_schema 正确', () => {
|
|
73
|
-
const tool = plugin.tool.tts_list_voices
|
|
74
|
-
expect(tool.description).toBe('列出可用的音色')
|
|
75
|
-
expect(tool.args).toBeDefined()
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('tts_list_providers: name, description, input_schema 正确', () => {
|
|
79
|
-
const tool = plugin.tool.tts_list_providers
|
|
80
|
-
expect(tool.description).toBe('列出所有已注册的 TTS 提供商')
|
|
81
|
-
expect(tool.args).toBeDefined()
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('tts_status: name, description, input_schema 正确', () => {
|
|
85
|
-
const tool = plugin.tool.tts_status
|
|
86
|
-
expect(tool.description).toBe('获取当前 TTS 播放状态')
|
|
87
|
-
expect(tool.args).toBeDefined()
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('tts_stream_speak: name, description, input_schema 正确', () => {
|
|
91
|
-
const tool = plugin.tool.tts_stream_speak
|
|
92
|
-
expect(tool.description).toBe('启动流式朗读(豆包模式),订阅AI回复并边生成边朗读')
|
|
93
|
-
expect(tool.args).toBeDefined()
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('tts_stream_stop: name, description, input_schema 正确', () => {
|
|
97
|
-
const tool = plugin.tool.tts_stream_stop
|
|
98
|
-
expect(tool.description).toBe('停止当前流式朗读')
|
|
99
|
-
expect(tool.args).toBeDefined()
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('tts_stream_status: name, description, input_schema 正确', () => {
|
|
103
|
-
const tool = plugin.tool.tts_stream_status
|
|
104
|
-
expect(tool.description).toBe('获取当前流式朗读状态')
|
|
105
|
-
expect(tool.args).toBeDefined()
|
|
106
|
-
})
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
describe('execute 函数验证', () => {
|
|
110
|
-
let plugin: any
|
|
111
|
-
|
|
112
|
-
beforeEach(async () => {
|
|
113
|
-
plugin = await OcosayPlugin({} as any, {})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('tts_speak: 调用 handleToolCall("tts_speak", args)', async () => {
|
|
117
|
-
const tool = plugin.tool.tts_speak
|
|
118
|
-
const args = { text: 'hello', provider: 'minimax' }
|
|
119
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Speech completed' })
|
|
120
|
-
|
|
121
|
-
await tool.execute(args)
|
|
122
|
-
|
|
123
|
-
expect(handleToolCall).toHaveBeenCalledWith('tts_speak', args)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('tts_stop: 调用 handleToolCall("tts_stop")', async () => {
|
|
127
|
-
const tool = plugin.tool.tts_stop
|
|
128
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Stopped' })
|
|
129
|
-
|
|
130
|
-
await tool.execute()
|
|
131
|
-
|
|
132
|
-
expect(handleToolCall).toHaveBeenCalledWith('tts_stop')
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
it('tts_pause: 调用 handleToolCall("tts_pause")', async () => {
|
|
136
|
-
const tool = plugin.tool.tts_pause
|
|
137
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Paused' })
|
|
138
|
-
|
|
139
|
-
await tool.execute()
|
|
140
|
-
|
|
141
|
-
expect(handleToolCall).toHaveBeenCalledWith('tts_pause')
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('tts_resume: 调用 handleToolCall("tts_resume")', async () => {
|
|
145
|
-
const tool = plugin.tool.tts_resume
|
|
146
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Resumed' })
|
|
147
|
-
|
|
148
|
-
await tool.execute()
|
|
149
|
-
|
|
150
|
-
expect(handleToolCall).toHaveBeenCalledWith('tts_resume')
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('tts_list_voices: 调用 handleToolCall("tts_list_voices", args)', async () => {
|
|
154
|
-
const tool = plugin.tool.tts_list_voices
|
|
155
|
-
const args = { provider: 'minimax' }
|
|
156
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, voices: [] })
|
|
157
|
-
|
|
158
|
-
await tool.execute(args)
|
|
159
|
-
|
|
160
|
-
expect(handleToolCall).toHaveBeenCalledWith('tts_list_voices', args)
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
it('tts_list_providers: 调用 handleToolCall("tts_list_providers")', async () => {
|
|
164
|
-
const tool = plugin.tool.tts_list_providers
|
|
165
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, providers: [] })
|
|
166
|
-
|
|
167
|
-
await tool.execute()
|
|
168
|
-
|
|
169
|
-
expect(handleToolCall).toHaveBeenCalledWith('tts_list_providers')
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('tts_status: 调用 handleToolCall("tts_status")', async () => {
|
|
173
|
-
const tool = plugin.tool.tts_status
|
|
174
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, isPlaying: false })
|
|
175
|
-
|
|
176
|
-
await tool.execute()
|
|
177
|
-
|
|
178
|
-
expect(handleToolCall).toHaveBeenCalledWith('tts_status')
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('tts_stream_speak: 调用 handleToolCall("tts_stream_speak", args)', async () => {
|
|
182
|
-
const tool = plugin.tool.tts_stream_speak
|
|
183
|
-
const args = { text: 'hello', voice: 'voice1', model: 'stream' }
|
|
184
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Stream started' })
|
|
185
|
-
|
|
186
|
-
await tool.execute(args)
|
|
187
|
-
|
|
188
|
-
expect(handleToolCall).toHaveBeenCalledWith('tts_stream_speak', args)
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('tts_stream_stop: 调用 handleToolCall("tts_stream_stop")', async () => {
|
|
192
|
-
const tool = plugin.tool.tts_stream_stop
|
|
193
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Stream stopped' })
|
|
194
|
-
|
|
195
|
-
await tool.execute()
|
|
196
|
-
|
|
197
|
-
expect(handleToolCall).toHaveBeenCalledWith('tts_stream_stop')
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it('tts_stream_status: 调用 handleToolCall("tts_stream_status")', async () => {
|
|
201
|
-
const tool = plugin.tool.tts_stream_status
|
|
202
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, isActive: false })
|
|
203
|
-
|
|
204
|
-
await tool.execute()
|
|
205
|
-
|
|
206
|
-
expect(handleToolCall).toHaveBeenCalledWith('tts_stream_status')
|
|
207
|
-
})
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
describe('错误处理', () => {
|
|
211
|
-
let plugin: any
|
|
212
|
-
|
|
213
|
-
beforeEach(async () => {
|
|
214
|
-
plugin = await OcosayPlugin({} as any, {})
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it('tts_speak: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
218
|
-
const tool = plugin.tool.tts_speak
|
|
219
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'TTS failed' })
|
|
220
|
-
|
|
221
|
-
await expect(tool.execute({ text: 'hello' })).rejects.toThrow('TTS failed')
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
it('tts_stop: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
225
|
-
const tool = plugin.tool.tts_stop
|
|
226
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Stop failed' })
|
|
227
|
-
|
|
228
|
-
await expect(tool.execute()).rejects.toThrow('Stop failed')
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
it('tts_pause: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
232
|
-
const tool = plugin.tool.tts_pause
|
|
233
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Pause failed' })
|
|
234
|
-
|
|
235
|
-
await expect(tool.execute()).rejects.toThrow('Pause failed')
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
it('tts_resume: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
239
|
-
const tool = plugin.tool.tts_resume
|
|
240
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Resume failed' })
|
|
241
|
-
|
|
242
|
-
await expect(tool.execute()).rejects.toThrow('Resume failed')
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('tts_list_voices: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
246
|
-
const tool = plugin.tool.tts_list_voices
|
|
247
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'List voices failed' })
|
|
248
|
-
|
|
249
|
-
await expect(tool.execute({})).rejects.toThrow('List voices failed')
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
it('tts_list_providers: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
253
|
-
const tool = plugin.tool.tts_list_providers
|
|
254
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'List providers failed' })
|
|
255
|
-
|
|
256
|
-
await expect(tool.execute()).rejects.toThrow('List providers failed')
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
it('tts_status: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
260
|
-
const tool = plugin.tool.tts_status
|
|
261
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Status failed' })
|
|
262
|
-
|
|
263
|
-
await expect(tool.execute()).rejects.toThrow('Status failed')
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
it('tts_stream_speak: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
267
|
-
const tool = plugin.tool.tts_stream_speak
|
|
268
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Stream speak failed' })
|
|
269
|
-
|
|
270
|
-
await expect(tool.execute({})).rejects.toThrow('Stream speak failed')
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
it('tts_stream_stop: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
274
|
-
const tool = plugin.tool.tts_stream_stop
|
|
275
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Stream stop failed' })
|
|
276
|
-
|
|
277
|
-
await expect(tool.execute()).rejects.toThrow('Stream stop failed')
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
it('tts_stream_status: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
281
|
-
const tool = plugin.tool.tts_stream_status
|
|
282
|
-
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Stream status failed' })
|
|
283
|
-
|
|
284
|
-
await expect(tool.execute()).rejects.toThrow('Stream status failed')
|
|
285
|
-
})
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
describe('ttsStreamStatusTool 特殊返回逻辑', () => {
|
|
289
|
-
let plugin: any
|
|
290
|
-
|
|
291
|
-
beforeEach(async () => {
|
|
292
|
-
plugin = await OcosayPlugin({} as any, {})
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
it('当 result 是 string 时直接返回', async () => {
|
|
296
|
-
const tool = plugin.tool.tts_stream_status
|
|
297
|
-
;(handleToolCall as jest.Mock).mockResolvedValue('stream_status_string')
|
|
298
|
-
|
|
299
|
-
const result = await tool.execute()
|
|
300
|
-
expect(result).toBe('stream_status_string')
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
it('当 result 是 object 时返回 JSON.stringify', async () => {
|
|
304
|
-
const tool = plugin.tool.tts_stream_status
|
|
305
|
-
const objResult = { isActive: true, bytesWritten: 100 }
|
|
306
|
-
;(handleToolCall as jest.Mock).mockResolvedValue(objResult)
|
|
307
|
-
|
|
308
|
-
const result = await tool.execute()
|
|
309
|
-
expect(result).toBe(JSON.stringify(objResult))
|
|
310
|
-
})
|
|
311
|
-
})
|
|
312
|
-
})
|
package/tests/provider.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"provider.test.d.ts","sourceRoot":"","sources":["provider.test.ts"],"names":[],"mappings":""}
|
package/tests/provider.test.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const base_1 = require("../src/providers/base");
|
|
4
|
-
const types_1 = require("../src/core/types");
|
|
5
|
-
// 创建测试 Provider
|
|
6
|
-
class TestProvider extends base_1.BaseTTSProvider {
|
|
7
|
-
name = 'test';
|
|
8
|
-
capabilities = { speak: true };
|
|
9
|
-
async doSpeak(text, voice, model) {
|
|
10
|
-
return {
|
|
11
|
-
audioData: Buffer.from([]),
|
|
12
|
-
format: 'mp3',
|
|
13
|
-
isStream: model === 'stream'
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
describe('Provider Registry', () => {
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
// 清理
|
|
20
|
-
(0, base_1.unregisterProvider)('test');
|
|
21
|
-
});
|
|
22
|
-
it('should register provider', () => {
|
|
23
|
-
const provider = new TestProvider();
|
|
24
|
-
(0, base_1.registerProvider)('test', provider);
|
|
25
|
-
expect((0, base_1.hasProvider)('test')).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
it('should get registered provider', () => {
|
|
28
|
-
const provider = new TestProvider();
|
|
29
|
-
(0, base_1.registerProvider)('test', provider);
|
|
30
|
-
const retrieved = (0, base_1.getProvider)('test');
|
|
31
|
-
expect(retrieved).toBe(provider);
|
|
32
|
-
});
|
|
33
|
-
it('should throw when getting non-existent provider', () => {
|
|
34
|
-
expect(() => (0, base_1.getProvider)('non-existent')).toThrow(types_1.TTSError);
|
|
35
|
-
});
|
|
36
|
-
it('should list all providers', () => {
|
|
37
|
-
(0, base_1.registerProvider)('test', new TestProvider());
|
|
38
|
-
const list = (0, base_1.listProviders)();
|
|
39
|
-
expect(list).toContain('test');
|
|
40
|
-
});
|
|
41
|
-
it('should unregister provider', () => {
|
|
42
|
-
(0, base_1.registerProvider)('test', new TestProvider());
|
|
43
|
-
(0, base_1.unregisterProvider)('test');
|
|
44
|
-
expect((0, base_1.hasProvider)('test')).toBe(false);
|
|
45
|
-
});
|
|
46
|
-
it('should throw when registering duplicate provider', () => {
|
|
47
|
-
(0, base_1.registerProvider)('test', new TestProvider());
|
|
48
|
-
expect(() => (0, base_1.registerProvider)('test', new TestProvider())).toThrow();
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
describe('BaseTTSProvider', () => {
|
|
52
|
-
let provider;
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
provider = new TestProvider();
|
|
55
|
-
});
|
|
56
|
-
it('should speak with options', async () => {
|
|
57
|
-
const result = await provider.speak('Hello', { model: 'sync' });
|
|
58
|
-
expect(result.format).toBe('mp3');
|
|
59
|
-
expect(result.isStream).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
it('should throw on empty text', async () => {
|
|
62
|
-
await expect(provider.speak('')).rejects.toThrow(types_1.TTSError);
|
|
63
|
-
await expect(provider.speak(' ')).rejects.toThrow(types_1.TTSError);
|
|
64
|
-
});
|
|
65
|
-
it('should stop without error', async () => {
|
|
66
|
-
await expect(provider.stop()).resolves.toBeUndefined();
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
//# sourceMappingURL=provider.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"provider.test.js","sourceRoot":"","sources":["provider.test.ts"],"names":[],"mappings":"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"}
|