@mingxy/ocosay 1.0.0
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 +556 -0
- package/TECH_PLAN.md +352 -0
- package/__mocks__/@opencode-ai/plugin.ts +32 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +95 -0
- package/dist/config.js.map +1 -0
- package/dist/core/backends/afplay-backend.d.ts +33 -0
- package/dist/core/backends/afplay-backend.d.ts.map +1 -0
- package/dist/core/backends/afplay-backend.js +144 -0
- package/dist/core/backends/afplay-backend.js.map +1 -0
- package/dist/core/backends/aplay-backend.d.ts +33 -0
- package/dist/core/backends/aplay-backend.d.ts.map +1 -0
- package/dist/core/backends/aplay-backend.js +142 -0
- package/dist/core/backends/aplay-backend.js.map +1 -0
- package/dist/core/backends/base.d.ts +94 -0
- package/dist/core/backends/base.d.ts.map +1 -0
- package/dist/core/backends/base.js +6 -0
- package/dist/core/backends/base.js.map +1 -0
- package/dist/core/backends/index.d.ts +29 -0
- package/dist/core/backends/index.d.ts.map +1 -0
- package/dist/core/backends/index.js +114 -0
- package/dist/core/backends/index.js.map +1 -0
- package/dist/core/backends/naudiodon-backend.d.ts +52 -0
- package/dist/core/backends/naudiodon-backend.d.ts.map +1 -0
- package/dist/core/backends/naudiodon-backend.js +123 -0
- package/dist/core/backends/naudiodon-backend.js.map +1 -0
- package/dist/core/backends/powershell-backend.d.ts +34 -0
- package/dist/core/backends/powershell-backend.d.ts.map +1 -0
- package/dist/core/backends/powershell-backend.js +154 -0
- package/dist/core/backends/powershell-backend.js.map +1 -0
- package/dist/core/player.d.ts +97 -0
- package/dist/core/player.d.ts.map +1 -0
- package/dist/core/player.js +268 -0
- package/dist/core/player.js.map +1 -0
- package/dist/core/speaker.d.ts +97 -0
- package/dist/core/speaker.d.ts.map +1 -0
- package/dist/core/speaker.js +218 -0
- package/dist/core/speaker.js.map +1 -0
- package/dist/core/stream-player.d.ts +107 -0
- package/dist/core/stream-player.d.ts.map +1 -0
- package/dist/core/stream-player.js +272 -0
- package/dist/core/stream-player.js.map +1 -0
- package/dist/core/stream-reader.d.ts +86 -0
- package/dist/core/stream-reader.d.ts.map +1 -0
- package/dist/core/stream-reader.js +172 -0
- package/dist/core/stream-reader.js.map +1 -0
- package/dist/core/streaming-synthesizer.d.ts +51 -0
- package/dist/core/streaming-synthesizer.d.ts.map +1 -0
- package/dist/core/streaming-synthesizer.js +103 -0
- package/dist/core/streaming-synthesizer.js.map +1 -0
- package/dist/core/types.d.ts +141 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +37 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +151 -0
- package/dist/plugin.js.map +1 -0
- package/dist/providers/base.d.ts +55 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +95 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/minimax.d.ts +84 -0
- package/dist/providers/minimax.d.ts.map +1 -0
- package/dist/providers/minimax.js +387 -0
- package/dist/providers/minimax.js.map +1 -0
- package/dist/tools/tts.d.ts +147 -0
- package/dist/tools/tts.d.ts.map +1 -0
- package/dist/tools/tts.js +232 -0
- package/dist/tools/tts.js.map +1 -0
- package/jest.config.js +15 -0
- package/package.json +49 -0
- package/src/config.ts +121 -0
- package/src/core/backends/afplay-backend.ts +162 -0
- package/src/core/backends/aplay-backend.ts +160 -0
- package/src/core/backends/base.ts +117 -0
- package/src/core/backends/index.ts +128 -0
- package/src/core/backends/naudiodon-backend.ts +164 -0
- package/src/core/backends/powershell-backend.ts +173 -0
- package/src/core/player.ts +322 -0
- package/src/core/speaker.ts +283 -0
- package/src/core/stream-player.ts +326 -0
- package/src/core/stream-reader.ts +190 -0
- package/src/core/streaming-synthesizer.ts +123 -0
- package/src/core/types.ts +185 -0
- package/src/index.ts +233 -0
- package/src/plugin.ts +166 -0
- package/src/providers/base.ts +150 -0
- package/src/providers/minimax.ts +515 -0
- package/src/tools/tts.ts +277 -0
- package/src/types/naudiodon.d.ts +19 -0
- package/tests/__mocks__/@opencode-ai/plugin.ts +32 -0
- package/tests/backends.test.ts +831 -0
- package/tests/index.test.ts +201 -0
- package/tests/integration-test.d.ts +6 -0
- package/tests/integration-test.d.ts.map +1 -0
- package/tests/integration-test.js +84 -0
- package/tests/integration-test.js.map +1 -0
- package/tests/integration-test.ts +93 -0
- package/tests/p1-fixes.test.ts +160 -0
- package/tests/plugin.test.ts +311 -0
- package/tests/provider.test.d.ts +2 -0
- package/tests/provider.test.d.ts.map +1 -0
- package/tests/provider.test.js +69 -0
- package/tests/provider.test.js.map +1 -0
- package/tests/provider.test.ts +87 -0
- package/tests/speaker.test.d.ts +2 -0
- package/tests/speaker.test.d.ts.map +1 -0
- package/tests/speaker.test.js +63 -0
- package/tests/speaker.test.js.map +1 -0
- package/tests/speaker.test.ts +232 -0
- package/tests/stream-player.test.ts +303 -0
- package/tests/stream-reader.test.ts +269 -0
- package/tests/streaming-synthesizer.test.ts +225 -0
- package/tests/tts-tools.test.ts +270 -0
- package/tests/types.test.d.ts +2 -0
- package/tests/types.test.d.ts.map +1 -0
- package/tests/types.test.js +61 -0
- package/tests/types.test.js.map +1 -0
- package/tests/types.test.ts +63 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,311 @@
|
|
|
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 OcosayPlugin from '../src/plugin'
|
|
33
|
+
|
|
34
|
+
describe('plugin.ts', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
jest.clearAllMocks()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('工具定义验证', () => {
|
|
40
|
+
let plugin: any
|
|
41
|
+
|
|
42
|
+
beforeEach(async () => {
|
|
43
|
+
plugin = await OcosayPlugin({} as any, {})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('tts_speak: name, description, input_schema 正确', () => {
|
|
47
|
+
const tool = plugin.tool.tts_speak
|
|
48
|
+
expect(tool.description).toBe('将文本转换为语音并播放')
|
|
49
|
+
expect(tool.args).toBeDefined()
|
|
50
|
+
expect(typeof tool.args).toBe('object')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('tts_stop: name, description, input_schema 正确', () => {
|
|
54
|
+
const tool = plugin.tool.tts_stop
|
|
55
|
+
expect(tool.description).toBe('停止当前 TTS 播放')
|
|
56
|
+
expect(tool.args).toBeDefined()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('tts_pause: name, description, input_schema 正确', () => {
|
|
60
|
+
const tool = plugin.tool.tts_pause
|
|
61
|
+
expect(tool.description).toBe('暂停当前 TTS 播放')
|
|
62
|
+
expect(tool.args).toBeDefined()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('tts_resume: name, description, input_schema 正确', () => {
|
|
66
|
+
const tool = plugin.tool.tts_resume
|
|
67
|
+
expect(tool.description).toBe('恢复暂停的 TTS 播放')
|
|
68
|
+
expect(tool.args).toBeDefined()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('tts_list_voices: name, description, input_schema 正确', () => {
|
|
72
|
+
const tool = plugin.tool.tts_list_voices
|
|
73
|
+
expect(tool.description).toBe('列出可用的音色')
|
|
74
|
+
expect(tool.args).toBeDefined()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('tts_list_providers: name, description, input_schema 正确', () => {
|
|
78
|
+
const tool = plugin.tool.tts_list_providers
|
|
79
|
+
expect(tool.description).toBe('列出所有已注册的 TTS 提供商')
|
|
80
|
+
expect(tool.args).toBeDefined()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('tts_status: name, description, input_schema 正确', () => {
|
|
84
|
+
const tool = plugin.tool.tts_status
|
|
85
|
+
expect(tool.description).toBe('获取当前 TTS 播放状态')
|
|
86
|
+
expect(tool.args).toBeDefined()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('tts_stream_speak: name, description, input_schema 正确', () => {
|
|
90
|
+
const tool = plugin.tool.tts_stream_speak
|
|
91
|
+
expect(tool.description).toBe('启动流式朗读(豆包模式),订阅AI回复并边生成边朗读')
|
|
92
|
+
expect(tool.args).toBeDefined()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('tts_stream_stop: name, description, input_schema 正确', () => {
|
|
96
|
+
const tool = plugin.tool.tts_stream_stop
|
|
97
|
+
expect(tool.description).toBe('停止当前流式朗读')
|
|
98
|
+
expect(tool.args).toBeDefined()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('tts_stream_status: name, description, input_schema 正确', () => {
|
|
102
|
+
const tool = plugin.tool.tts_stream_status
|
|
103
|
+
expect(tool.description).toBe('获取当前流式朗读状态')
|
|
104
|
+
expect(tool.args).toBeDefined()
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('execute 函数验证', () => {
|
|
109
|
+
let plugin: any
|
|
110
|
+
|
|
111
|
+
beforeEach(async () => {
|
|
112
|
+
plugin = await OcosayPlugin({} as any, {})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('tts_speak: 调用 handleToolCall("tts_speak", args)', async () => {
|
|
116
|
+
const tool = plugin.tool.tts_speak
|
|
117
|
+
const args = { text: 'hello', provider: 'minimax' }
|
|
118
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Speech completed' })
|
|
119
|
+
|
|
120
|
+
await tool.execute(args)
|
|
121
|
+
|
|
122
|
+
expect(handleToolCall).toHaveBeenCalledWith('tts_speak', args)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('tts_stop: 调用 handleToolCall("tts_stop")', async () => {
|
|
126
|
+
const tool = plugin.tool.tts_stop
|
|
127
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Stopped' })
|
|
128
|
+
|
|
129
|
+
await tool.execute()
|
|
130
|
+
|
|
131
|
+
expect(handleToolCall).toHaveBeenCalledWith('tts_stop')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('tts_pause: 调用 handleToolCall("tts_pause")', async () => {
|
|
135
|
+
const tool = plugin.tool.tts_pause
|
|
136
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Paused' })
|
|
137
|
+
|
|
138
|
+
await tool.execute()
|
|
139
|
+
|
|
140
|
+
expect(handleToolCall).toHaveBeenCalledWith('tts_pause')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('tts_resume: 调用 handleToolCall("tts_resume")', async () => {
|
|
144
|
+
const tool = plugin.tool.tts_resume
|
|
145
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Resumed' })
|
|
146
|
+
|
|
147
|
+
await tool.execute()
|
|
148
|
+
|
|
149
|
+
expect(handleToolCall).toHaveBeenCalledWith('tts_resume')
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('tts_list_voices: 调用 handleToolCall("tts_list_voices", args)', async () => {
|
|
153
|
+
const tool = plugin.tool.tts_list_voices
|
|
154
|
+
const args = { provider: 'minimax' }
|
|
155
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, voices: [] })
|
|
156
|
+
|
|
157
|
+
await tool.execute(args)
|
|
158
|
+
|
|
159
|
+
expect(handleToolCall).toHaveBeenCalledWith('tts_list_voices', args)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('tts_list_providers: 调用 handleToolCall("tts_list_providers")', async () => {
|
|
163
|
+
const tool = plugin.tool.tts_list_providers
|
|
164
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, providers: [] })
|
|
165
|
+
|
|
166
|
+
await tool.execute()
|
|
167
|
+
|
|
168
|
+
expect(handleToolCall).toHaveBeenCalledWith('tts_list_providers')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('tts_status: 调用 handleToolCall("tts_status")', async () => {
|
|
172
|
+
const tool = plugin.tool.tts_status
|
|
173
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, isPlaying: false })
|
|
174
|
+
|
|
175
|
+
await tool.execute()
|
|
176
|
+
|
|
177
|
+
expect(handleToolCall).toHaveBeenCalledWith('tts_status')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('tts_stream_speak: 调用 handleToolCall("tts_stream_speak", args)', async () => {
|
|
181
|
+
const tool = plugin.tool.tts_stream_speak
|
|
182
|
+
const args = { text: 'hello', voice: 'voice1', model: 'stream' }
|
|
183
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Stream started' })
|
|
184
|
+
|
|
185
|
+
await tool.execute(args)
|
|
186
|
+
|
|
187
|
+
expect(handleToolCall).toHaveBeenCalledWith('tts_stream_speak', args)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('tts_stream_stop: 调用 handleToolCall("tts_stream_stop")', async () => {
|
|
191
|
+
const tool = plugin.tool.tts_stream_stop
|
|
192
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, message: 'Stream stopped' })
|
|
193
|
+
|
|
194
|
+
await tool.execute()
|
|
195
|
+
|
|
196
|
+
expect(handleToolCall).toHaveBeenCalledWith('tts_stream_stop')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('tts_stream_status: 调用 handleToolCall("tts_stream_status")', async () => {
|
|
200
|
+
const tool = plugin.tool.tts_stream_status
|
|
201
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: true, isActive: false })
|
|
202
|
+
|
|
203
|
+
await tool.execute()
|
|
204
|
+
|
|
205
|
+
expect(handleToolCall).toHaveBeenCalledWith('tts_stream_status')
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
describe('错误处理', () => {
|
|
210
|
+
let plugin: any
|
|
211
|
+
|
|
212
|
+
beforeEach(async () => {
|
|
213
|
+
plugin = await OcosayPlugin({} as any, {})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('tts_speak: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
217
|
+
const tool = plugin.tool.tts_speak
|
|
218
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'TTS failed' })
|
|
219
|
+
|
|
220
|
+
await expect(tool.execute({ text: 'hello' })).rejects.toThrow('TTS failed')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('tts_stop: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
224
|
+
const tool = plugin.tool.tts_stop
|
|
225
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Stop failed' })
|
|
226
|
+
|
|
227
|
+
await expect(tool.execute()).rejects.toThrow('Stop failed')
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('tts_pause: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
231
|
+
const tool = plugin.tool.tts_pause
|
|
232
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Pause failed' })
|
|
233
|
+
|
|
234
|
+
await expect(tool.execute()).rejects.toThrow('Pause failed')
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('tts_resume: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
238
|
+
const tool = plugin.tool.tts_resume
|
|
239
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Resume failed' })
|
|
240
|
+
|
|
241
|
+
await expect(tool.execute()).rejects.toThrow('Resume failed')
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('tts_list_voices: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
245
|
+
const tool = plugin.tool.tts_list_voices
|
|
246
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'List voices failed' })
|
|
247
|
+
|
|
248
|
+
await expect(tool.execute({})).rejects.toThrow('List voices failed')
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('tts_list_providers: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
252
|
+
const tool = plugin.tool.tts_list_providers
|
|
253
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'List providers failed' })
|
|
254
|
+
|
|
255
|
+
await expect(tool.execute()).rejects.toThrow('List providers failed')
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('tts_status: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
259
|
+
const tool = plugin.tool.tts_status
|
|
260
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Status failed' })
|
|
261
|
+
|
|
262
|
+
await expect(tool.execute()).rejects.toThrow('Status failed')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('tts_stream_speak: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
266
|
+
const tool = plugin.tool.tts_stream_speak
|
|
267
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Stream speak failed' })
|
|
268
|
+
|
|
269
|
+
await expect(tool.execute({})).rejects.toThrow('Stream speak failed')
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('tts_stream_stop: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
273
|
+
const tool = plugin.tool.tts_stream_stop
|
|
274
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Stream stop failed' })
|
|
275
|
+
|
|
276
|
+
await expect(tool.execute()).rejects.toThrow('Stream stop failed')
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('tts_stream_status: handleToolCall 返回 success: false 时抛出 Error', async () => {
|
|
280
|
+
const tool = plugin.tool.tts_stream_status
|
|
281
|
+
;(handleToolCall as jest.Mock).mockResolvedValue({ success: false, error: 'Stream status failed' })
|
|
282
|
+
|
|
283
|
+
await expect(tool.execute()).rejects.toThrow('Stream status failed')
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
describe('ttsStreamStatusTool 特殊返回逻辑', () => {
|
|
288
|
+
let plugin: any
|
|
289
|
+
|
|
290
|
+
beforeEach(async () => {
|
|
291
|
+
plugin = await OcosayPlugin({} as any, {})
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('当 result 是 string 时直接返回', async () => {
|
|
295
|
+
const tool = plugin.tool.tts_stream_status
|
|
296
|
+
;(handleToolCall as jest.Mock).mockResolvedValue('stream_status_string')
|
|
297
|
+
|
|
298
|
+
const result = await tool.execute()
|
|
299
|
+
expect(result).toBe('stream_status_string')
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('当 result 是 object 时返回 JSON.stringify', async () => {
|
|
303
|
+
const tool = plugin.tool.tts_stream_status
|
|
304
|
+
const objResult = { isActive: true, bytesWritten: 100 }
|
|
305
|
+
;(handleToolCall as jest.Mock).mockResolvedValue(objResult)
|
|
306
|
+
|
|
307
|
+
const result = await tool.execute()
|
|
308
|
+
expect(result).toBe(JSON.stringify(objResult))
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.test.d.ts","sourceRoot":"","sources":["provider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.test.js","sourceRoot":"","sources":["provider.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
registerProvider,
|
|
3
|
+
getProvider,
|
|
4
|
+
listProviders,
|
|
5
|
+
hasProvider,
|
|
6
|
+
unregisterProvider,
|
|
7
|
+
BaseTTSProvider
|
|
8
|
+
} from '../src/providers/base'
|
|
9
|
+
import { TTSError, TTSErrorCode, TTSCapabilities } from '../src/core/types'
|
|
10
|
+
|
|
11
|
+
// 创建测试 Provider
|
|
12
|
+
class TestProvider extends BaseTTSProvider {
|
|
13
|
+
name = 'test'
|
|
14
|
+
capabilities: TTSCapabilities = { speak: true }
|
|
15
|
+
|
|
16
|
+
protected async doSpeak(text: string, voice: string | undefined, model: any) {
|
|
17
|
+
return {
|
|
18
|
+
audioData: Buffer.from([]),
|
|
19
|
+
format: 'mp3',
|
|
20
|
+
isStream: model === 'stream'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('Provider Registry', () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
// 清理
|
|
28
|
+
unregisterProvider('test')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should register provider', () => {
|
|
32
|
+
const provider = new TestProvider()
|
|
33
|
+
registerProvider('test', provider)
|
|
34
|
+
expect(hasProvider('test')).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should get registered provider', () => {
|
|
38
|
+
const provider = new TestProvider()
|
|
39
|
+
registerProvider('test', provider)
|
|
40
|
+
const retrieved = getProvider('test')
|
|
41
|
+
expect(retrieved).toBe(provider)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should throw when getting non-existent provider', () => {
|
|
45
|
+
expect(() => getProvider('non-existent')).toThrow(TTSError)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should list all providers', () => {
|
|
49
|
+
registerProvider('test', new TestProvider())
|
|
50
|
+
const list = listProviders()
|
|
51
|
+
expect(list).toContain('test')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('should unregister provider', () => {
|
|
55
|
+
registerProvider('test', new TestProvider())
|
|
56
|
+
unregisterProvider('test')
|
|
57
|
+
expect(hasProvider('test')).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should throw when registering duplicate provider', () => {
|
|
61
|
+
registerProvider('test', new TestProvider())
|
|
62
|
+
expect(() => registerProvider('test', new TestProvider())).toThrow()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('BaseTTSProvider', () => {
|
|
67
|
+
let provider: TestProvider
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
provider = new TestProvider()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should speak with options', async () => {
|
|
74
|
+
const result = await provider.speak('Hello', { model: 'sync' })
|
|
75
|
+
expect(result.format).toBe('mp3')
|
|
76
|
+
expect(result.isStream).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should throw on empty text', async () => {
|
|
80
|
+
await expect(provider.speak('')).rejects.toThrow(TTSError)
|
|
81
|
+
await expect(provider.speak(' ')).rejects.toThrow(TTSError)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should stop without error', async () => {
|
|
85
|
+
await expect(provider.stop()).resolves.toBeUndefined()
|
|
86
|
+
})
|
|
87
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speaker.test.d.ts","sourceRoot":"","sources":["speaker.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const speaker_1 = require("../src/core/speaker");
|
|
4
|
+
const types_1 = require("../src/core/types");
|
|
5
|
+
const base_1 = require("../src/providers/base");
|
|
6
|
+
// Mock Provider
|
|
7
|
+
class MockProvider extends base_1.BaseTTSProvider {
|
|
8
|
+
name = 'mock';
|
|
9
|
+
capabilities = { speak: true, stream: true };
|
|
10
|
+
async doSpeak(text, voice, model) {
|
|
11
|
+
return {
|
|
12
|
+
audioData: Buffer.from([1, 2, 3]),
|
|
13
|
+
format: 'mp3',
|
|
14
|
+
isStream: model === 'stream',
|
|
15
|
+
duration: 1.0
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
describe('Speaker', () => {
|
|
20
|
+
let speaker;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
(0, base_1.registerProvider)('mock', new MockProvider());
|
|
23
|
+
speaker = new speaker_1.Speaker({ defaultProvider: 'mock' });
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
(0, base_1.unregisterProvider)('mock');
|
|
27
|
+
});
|
|
28
|
+
describe('initialization', () => {
|
|
29
|
+
it('should create speaker with options', () => {
|
|
30
|
+
const s = new speaker_1.Speaker({ defaultProvider: 'mock', defaultModel: 'sync' });
|
|
31
|
+
expect(s.getProviders()).toContain('mock');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('speak', () => {
|
|
35
|
+
it('should throw on empty text', async () => {
|
|
36
|
+
await expect(speaker.speak('')).rejects.toThrow(types_1.TTSError);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('control methods', () => {
|
|
40
|
+
it('should pause without error', () => {
|
|
41
|
+
expect(() => speaker.pause()).not.toThrow();
|
|
42
|
+
});
|
|
43
|
+
it('should resume without error', () => {
|
|
44
|
+
expect(() => speaker.resume()).not.toThrow();
|
|
45
|
+
});
|
|
46
|
+
it('should stop without error', async () => {
|
|
47
|
+
await expect(speaker.stop()).resolves.toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('listVoices', () => {
|
|
51
|
+
it('should return voices from provider', async () => {
|
|
52
|
+
const voices = await speaker.listVoices('mock');
|
|
53
|
+
expect(Array.isArray(voices)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('getCapabilities', () => {
|
|
57
|
+
it('should return provider capabilities', () => {
|
|
58
|
+
const caps = speaker.getCapabilities('mock');
|
|
59
|
+
expect(caps.speak).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=speaker.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speaker.test.js","sourceRoot":"","sources":["speaker.test.ts"],"names":[],"mappings":"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"}
|