@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.
Files changed (126) hide show
  1. package/README.md +556 -0
  2. package/TECH_PLAN.md +352 -0
  3. package/__mocks__/@opencode-ai/plugin.ts +32 -0
  4. package/dist/config.d.ts +26 -0
  5. package/dist/config.d.ts.map +1 -0
  6. package/dist/config.js +95 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/core/backends/afplay-backend.d.ts +33 -0
  9. package/dist/core/backends/afplay-backend.d.ts.map +1 -0
  10. package/dist/core/backends/afplay-backend.js +144 -0
  11. package/dist/core/backends/afplay-backend.js.map +1 -0
  12. package/dist/core/backends/aplay-backend.d.ts +33 -0
  13. package/dist/core/backends/aplay-backend.d.ts.map +1 -0
  14. package/dist/core/backends/aplay-backend.js +142 -0
  15. package/dist/core/backends/aplay-backend.js.map +1 -0
  16. package/dist/core/backends/base.d.ts +94 -0
  17. package/dist/core/backends/base.d.ts.map +1 -0
  18. package/dist/core/backends/base.js +6 -0
  19. package/dist/core/backends/base.js.map +1 -0
  20. package/dist/core/backends/index.d.ts +29 -0
  21. package/dist/core/backends/index.d.ts.map +1 -0
  22. package/dist/core/backends/index.js +114 -0
  23. package/dist/core/backends/index.js.map +1 -0
  24. package/dist/core/backends/naudiodon-backend.d.ts +52 -0
  25. package/dist/core/backends/naudiodon-backend.d.ts.map +1 -0
  26. package/dist/core/backends/naudiodon-backend.js +123 -0
  27. package/dist/core/backends/naudiodon-backend.js.map +1 -0
  28. package/dist/core/backends/powershell-backend.d.ts +34 -0
  29. package/dist/core/backends/powershell-backend.d.ts.map +1 -0
  30. package/dist/core/backends/powershell-backend.js +154 -0
  31. package/dist/core/backends/powershell-backend.js.map +1 -0
  32. package/dist/core/player.d.ts +97 -0
  33. package/dist/core/player.d.ts.map +1 -0
  34. package/dist/core/player.js +268 -0
  35. package/dist/core/player.js.map +1 -0
  36. package/dist/core/speaker.d.ts +97 -0
  37. package/dist/core/speaker.d.ts.map +1 -0
  38. package/dist/core/speaker.js +218 -0
  39. package/dist/core/speaker.js.map +1 -0
  40. package/dist/core/stream-player.d.ts +107 -0
  41. package/dist/core/stream-player.d.ts.map +1 -0
  42. package/dist/core/stream-player.js +272 -0
  43. package/dist/core/stream-player.js.map +1 -0
  44. package/dist/core/stream-reader.d.ts +86 -0
  45. package/dist/core/stream-reader.d.ts.map +1 -0
  46. package/dist/core/stream-reader.js +172 -0
  47. package/dist/core/stream-reader.js.map +1 -0
  48. package/dist/core/streaming-synthesizer.d.ts +51 -0
  49. package/dist/core/streaming-synthesizer.d.ts.map +1 -0
  50. package/dist/core/streaming-synthesizer.js +103 -0
  51. package/dist/core/streaming-synthesizer.js.map +1 -0
  52. package/dist/core/types.d.ts +141 -0
  53. package/dist/core/types.d.ts.map +1 -0
  54. package/dist/core/types.js +37 -0
  55. package/dist/core/types.js.map +1 -0
  56. package/dist/index.d.ts +40 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +179 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/plugin.d.ts +4 -0
  61. package/dist/plugin.d.ts.map +1 -0
  62. package/dist/plugin.js +151 -0
  63. package/dist/plugin.js.map +1 -0
  64. package/dist/providers/base.d.ts +55 -0
  65. package/dist/providers/base.d.ts.map +1 -0
  66. package/dist/providers/base.js +95 -0
  67. package/dist/providers/base.js.map +1 -0
  68. package/dist/providers/minimax.d.ts +84 -0
  69. package/dist/providers/minimax.d.ts.map +1 -0
  70. package/dist/providers/minimax.js +387 -0
  71. package/dist/providers/minimax.js.map +1 -0
  72. package/dist/tools/tts.d.ts +147 -0
  73. package/dist/tools/tts.d.ts.map +1 -0
  74. package/dist/tools/tts.js +232 -0
  75. package/dist/tools/tts.js.map +1 -0
  76. package/jest.config.js +15 -0
  77. package/package.json +49 -0
  78. package/src/config.ts +121 -0
  79. package/src/core/backends/afplay-backend.ts +162 -0
  80. package/src/core/backends/aplay-backend.ts +160 -0
  81. package/src/core/backends/base.ts +117 -0
  82. package/src/core/backends/index.ts +128 -0
  83. package/src/core/backends/naudiodon-backend.ts +164 -0
  84. package/src/core/backends/powershell-backend.ts +173 -0
  85. package/src/core/player.ts +322 -0
  86. package/src/core/speaker.ts +283 -0
  87. package/src/core/stream-player.ts +326 -0
  88. package/src/core/stream-reader.ts +190 -0
  89. package/src/core/streaming-synthesizer.ts +123 -0
  90. package/src/core/types.ts +185 -0
  91. package/src/index.ts +233 -0
  92. package/src/plugin.ts +166 -0
  93. package/src/providers/base.ts +150 -0
  94. package/src/providers/minimax.ts +515 -0
  95. package/src/tools/tts.ts +277 -0
  96. package/src/types/naudiodon.d.ts +19 -0
  97. package/tests/__mocks__/@opencode-ai/plugin.ts +32 -0
  98. package/tests/backends.test.ts +831 -0
  99. package/tests/index.test.ts +201 -0
  100. package/tests/integration-test.d.ts +6 -0
  101. package/tests/integration-test.d.ts.map +1 -0
  102. package/tests/integration-test.js +84 -0
  103. package/tests/integration-test.js.map +1 -0
  104. package/tests/integration-test.ts +93 -0
  105. package/tests/p1-fixes.test.ts +160 -0
  106. package/tests/plugin.test.ts +311 -0
  107. package/tests/provider.test.d.ts +2 -0
  108. package/tests/provider.test.d.ts.map +1 -0
  109. package/tests/provider.test.js +69 -0
  110. package/tests/provider.test.js.map +1 -0
  111. package/tests/provider.test.ts +87 -0
  112. package/tests/speaker.test.d.ts +2 -0
  113. package/tests/speaker.test.d.ts.map +1 -0
  114. package/tests/speaker.test.js +63 -0
  115. package/tests/speaker.test.js.map +1 -0
  116. package/tests/speaker.test.ts +232 -0
  117. package/tests/stream-player.test.ts +303 -0
  118. package/tests/stream-reader.test.ts +269 -0
  119. package/tests/streaming-synthesizer.test.ts +225 -0
  120. package/tests/tts-tools.test.ts +270 -0
  121. package/tests/types.test.d.ts +2 -0
  122. package/tests/types.test.d.ts.map +1 -0
  123. package/tests/types.test.js +61 -0
  124. package/tests/types.test.js.map +1 -0
  125. package/tests/types.test.ts +63 -0
  126. package/tsconfig.json +22 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * OpenCode TTS 工具定义
3
+ * 用于 OpenCode Plugin 注册
4
+ */
5
+
6
+ import { speak, stop, pause, resume, listVoices, getDefaultSpeaker } from '../core/speaker'
7
+ import { TTSError, TTSErrorCode } from '../core/types'
8
+ import {
9
+ isStreamEnabled,
10
+ isAutoReadEnabled,
11
+ getStreamStatus,
12
+ getStreamReader,
13
+ getStreamingSynthesizer,
14
+ getStreamPlayer
15
+ } from '../index'
16
+
17
+ /**
18
+ * OpenCode TTS 工具定义
19
+ * 用于 OpenCode Plugin 注册
20
+ */
21
+ export const ttsTools = [
22
+ {
23
+ name: 'tts_speak',
24
+ description: '将文本转换为语音并播放',
25
+ input: {
26
+ type: 'object',
27
+ properties: {
28
+ text: {
29
+ type: 'string',
30
+ description: '要转换的文本内容'
31
+ },
32
+ provider: {
33
+ type: 'string',
34
+ description: 'TTS 提供商名称',
35
+ default: 'minimax'
36
+ },
37
+ voice: {
38
+ type: 'string',
39
+ description: '音色 ID'
40
+ },
41
+ model: {
42
+ type: 'string',
43
+ enum: ['sync', 'async', 'stream'],
44
+ description: '合成模式',
45
+ default: 'stream'
46
+ },
47
+ speed: {
48
+ type: 'number',
49
+ description: '语速 0.5-2.0'
50
+ },
51
+ volume: {
52
+ type: 'number',
53
+ description: '音量 0-100'
54
+ },
55
+ pitch: {
56
+ type: 'number',
57
+ description: '音调 0.5-2.0'
58
+ }
59
+ },
60
+ required: ['text']
61
+ }
62
+ },
63
+ {
64
+ name: 'tts_stop',
65
+ description: '停止当前 TTS 播放'
66
+ },
67
+ {
68
+ name: 'tts_pause',
69
+ description: '暂停当前 TTS 播放'
70
+ },
71
+ {
72
+ name: 'tts_resume',
73
+ description: '恢复暂停的 TTS 播放'
74
+ },
75
+ {
76
+ name: 'tts_list_voices',
77
+ description: '列出可用的音色',
78
+ input: {
79
+ type: 'object',
80
+ properties: {
81
+ provider: {
82
+ type: 'string',
83
+ description: 'TTS 提供商名称',
84
+ default: 'minimax'
85
+ }
86
+ }
87
+ }
88
+ },
89
+ {
90
+ name: 'tts_list_providers',
91
+ description: '列出所有已注册的 TTS 提供商'
92
+ },
93
+ {
94
+ name: 'tts_status',
95
+ description: '获取当前 TTS 播放状态',
96
+ output: {
97
+ type: 'object',
98
+ properties: {
99
+ isPlaying: { type: 'boolean' },
100
+ isPaused: { type: 'boolean' }
101
+ }
102
+ }
103
+ },
104
+ {
105
+ name: 'tts_stream_speak',
106
+ description: '启动流式朗读(豆包模式),订阅AI回复并边生成边朗读',
107
+ input: {
108
+ type: 'object',
109
+ properties: {
110
+ text: {
111
+ type: 'string',
112
+ description: '初始文本(可选)'
113
+ },
114
+ voice: {
115
+ type: 'string',
116
+ description: '音色ID'
117
+ },
118
+ model: {
119
+ type: 'string',
120
+ enum: ['sync', 'async', 'stream'],
121
+ default: 'stream'
122
+ }
123
+ }
124
+ }
125
+ },
126
+ {
127
+ name: 'tts_stream_stop',
128
+ description: '停止当前流式朗读'
129
+ },
130
+ {
131
+ name: 'tts_stream_status',
132
+ description: '获取当前流式朗读状态',
133
+ output: {
134
+ type: 'object',
135
+ properties: {
136
+ isActive: { type: 'boolean' },
137
+ bytesWritten: { type: 'number' },
138
+ state: { type: 'string' }
139
+ }
140
+ }
141
+ }
142
+ ]
143
+
144
+ /**
145
+ * 工具执行处理器
146
+ */
147
+ export async function handleToolCall(
148
+ toolName: string,
149
+ args?: Record<string, any>
150
+ ): Promise<any> {
151
+ try {
152
+ switch (toolName) {
153
+ case 'tts_speak':
154
+ await speak(args?.text, {
155
+ provider: args?.provider,
156
+ voice: args?.voice,
157
+ model: args?.model,
158
+ speed: args?.speed,
159
+ volume: args?.volume,
160
+ pitch: args?.pitch
161
+ })
162
+ return { success: true, message: 'Speech completed' }
163
+
164
+ case 'tts_stop':
165
+ await stop()
166
+ return { success: true, message: 'Stopped' }
167
+
168
+ case 'tts_pause':
169
+ pause()
170
+ return { success: true, message: 'Paused' }
171
+
172
+ case 'tts_resume':
173
+ resume()
174
+ return { success: true, message: 'Resumed' }
175
+
176
+ case 'tts_list_voices':
177
+ const voices = await listVoices(args?.provider as string | undefined)
178
+ return { success: true, voices }
179
+
180
+ case 'tts_list_providers':
181
+ const speaker = getDefaultSpeaker()
182
+ const providers = speaker.getProviders()
183
+ return { success: true, providers }
184
+
185
+ case 'tts_status':
186
+ const s = getDefaultSpeaker()
187
+ return {
188
+ success: true,
189
+ isPlaying: s.isPlaying(),
190
+ isPaused: s.isPausedState()
191
+ }
192
+
193
+ case 'tts_stream_speak':
194
+ if (!isAutoReadEnabled()) {
195
+ throw new TTSError(
196
+ 'Stream mode is not enabled. autoRead must be enabled in configuration to use tts_stream_speak.',
197
+ TTSErrorCode.UNKNOWN,
198
+ 'tts_stream'
199
+ )
200
+ }
201
+ if (!isStreamEnabled()) {
202
+ throw new TTSError(
203
+ 'Stream components not initialized. Please initialize with autoRead enabled.',
204
+ TTSErrorCode.UNKNOWN,
205
+ 'tts_stream'
206
+ )
207
+ }
208
+ const streamReader = getStreamReader()
209
+ const synthesizer = getStreamingSynthesizer()
210
+ if (streamReader && synthesizer) {
211
+ streamReader.start()
212
+ if (args?.text) {
213
+ synthesizer.synthesize(args.text)
214
+ }
215
+ return { success: true, message: 'Stream speak started' }
216
+ }
217
+ throw new TTSError(
218
+ 'Stream components not available',
219
+ TTSErrorCode.UNKNOWN,
220
+ 'tts_stream'
221
+ )
222
+
223
+ case 'tts_stream_stop':
224
+ if (!isStreamEnabled()) {
225
+ throw new TTSError(
226
+ 'Stream mode is not enabled. Please enable autoRead in configuration.',
227
+ TTSErrorCode.UNKNOWN,
228
+ 'tts_stream'
229
+ )
230
+ }
231
+ const player = getStreamPlayer()
232
+ if (player) {
233
+ player.stop()
234
+ return { success: true, message: 'Stream stopped' }
235
+ }
236
+ throw new TTSError(
237
+ 'Stream player not available',
238
+ TTSErrorCode.UNKNOWN,
239
+ 'tts_stream'
240
+ )
241
+
242
+ case 'tts_stream_status':
243
+ if (!isStreamEnabled()) {
244
+ return {
245
+ success: true,
246
+ isActive: false,
247
+ bytesWritten: 0,
248
+ state: 'not_initialized'
249
+ }
250
+ }
251
+ return {
252
+ success: true,
253
+ ...getStreamStatus()
254
+ }
255
+
256
+ default:
257
+ throw new TTSError(
258
+ `Unknown tool: ${toolName}`,
259
+ TTSErrorCode.UNKNOWN,
260
+ 'tools'
261
+ )
262
+ }
263
+ } catch (error) {
264
+ if (error instanceof TTSError) {
265
+ return {
266
+ success: false,
267
+ error: error.message,
268
+ code: error.code,
269
+ provider: error.provider
270
+ }
271
+ }
272
+ return {
273
+ success: false,
274
+ error: String(error)
275
+ }
276
+ }
277
+ }
@@ -0,0 +1,19 @@
1
+ declare module 'naudiodon' {
2
+ interface NaudiodonAudioOutput {
3
+ start(): void
4
+ write(chunk: Buffer): void
5
+ end(): void
6
+ quit(): void
7
+ on(event: string, callback: (error: Error) => void): void
8
+ }
9
+
10
+ interface Naudiodon {
11
+ new (options: {
12
+ sampleRate?: number
13
+ channels?: number
14
+ bitDepth?: number
15
+ }): NaudiodonAudioOutput
16
+ }
17
+
18
+ export = Naudiodon
19
+ }
@@ -0,0 +1,32 @@
1
+ const schema = {
2
+ string: () => ({
3
+ describe: (desc: string) => ({ describe: desc }),
4
+ optional: () => ({
5
+ describe: (desc: string) => ({ describe: desc })
6
+ }),
7
+ number: () => ({
8
+ describe: (desc: string) => ({ describe: desc }),
9
+ optional: () => ({
10
+ describe: (desc: string) => ({ describe: desc })
11
+ })
12
+ }),
13
+ enum: (values: string[]) => ({
14
+ describe: (desc: string) => ({ describe: desc }),
15
+ optional: () => ({
16
+ describe: (desc: string) => ({ describe: desc })
17
+ })
18
+ })
19
+ })
20
+ }
21
+
22
+ function tool({ description, args, execute }: any) {
23
+ return {
24
+ description,
25
+ args,
26
+ execute,
27
+ schema
28
+ }
29
+ }
30
+
31
+ export { tool, schema }
32
+ export const Plugin = jest.fn()