@deepseek-ai/dsh-experimental-speech-to-text 0.1.7-alpha.1
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +80 -0
- package/README.zh.md +80 -0
- package/lib/index.js +200 -0
- package/lib/types/index.d.ts +91 -0
- package/lib/types/index.js +181 -0
- package/lib/types/types.d.ts +119 -0
- package/lib/types/types.js +2 -0
- package/lib/types/wave.d.ts +9 -0
- package/lib/types/wave.js +24 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/experimental/speech-to-text/README.md
|
|
5
|
+
README.md: 64202aba4ae7e0ffc47c86243d818caa8f85a9e4
|
|
6
|
+
README.zh.md: 38466f18a0670a252958c6b51c17457fcf80f14c
|
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Named experimental transcription providers and cancellation ownership."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-experimental-speech-to-text
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
This Service Definition selects named speech recognizers through `ctx.speechToText`. Consumers resolve their request before execution; providers register independently.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Load through the [voice input bundle](../voice-input-bundle/README.md), or compose the service with a provider and consumer. `defaultProvider` is required and selects an exact registered id; the bundle supplies `sensevoice-local`. `language` supplies the omitted language hint. A missing or duplicate provider fails explicitly.
|
|
29
|
+
|
|
30
|
+
-----
|
|
31
|
+
|
|
32
|
+
<a id="understand-the-implementation"></a>
|
|
33
|
+
## Understand the implementation
|
|
34
|
+
|
|
35
|
+
<details>
|
|
36
|
+
<summary>Maintainer details — click to expand</summary>
|
|
37
|
+
|
|
38
|
+
`resolve()` captures the provider instance, recording and language. `transcribe()` rejects a withdrawn or replaced registration. A registration disposer closes admission, aborts accepted requests and joins provider settlement; providers must honor cancellation. No fallback selects a different recognizer or uploads audio. No runtime invariant companion is published because the registry is the sole source of provider and preparation observations.
|
|
39
|
+
|
|
40
|
+
`defaultProvider` and `language` are volatile Config fields: `configure()` writes the supplied fields into this plugin's profile entry through the `settings` service, and the running instance reads the updated values without remounting; composition defaults apply until an override is saved. `configure()` fails without `settings` or a profile entry, and rejects a language unsupported by the selected provider before saving. Providers advertise accepted language hints through `languages`; `resolve()` validates the selected hint before transcription. Provider-owned preparation is observed through complete `SpeechSnapshot` values; closing an observer never cancels preparation.
|
|
41
|
+
|
|
42
|
+
The `./wave` helper validates canonical 16 kHz mono PCM16 WAV for the Remote consumer and the native recognition process. Both reject inconsistent headers and lengths before decoding samples.
|
|
43
|
+
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
-----
|
|
47
|
+
|
|
48
|
+
<a id="further-exploration"></a>
|
|
49
|
+
## Further Exploration
|
|
50
|
+
|
|
51
|
+
[Voice input subsystem](../../../docs/subsystems/voice-input.md)
|
|
52
|
+
|
|
53
|
+
-----
|
|
54
|
+
|
|
55
|
+
<a id="model-experience"></a>
|
|
56
|
+
## Model Experience
|
|
57
|
+
|
|
58
|
+
None, as recordings and preparation remain outside model requests; ordinary user submission owns any later text.
|
|
59
|
+
|
|
60
|
+
#### KV Cache effect
|
|
61
|
+
|
|
62
|
+
No direct effect; ordinary submission owns the message content.
|
|
63
|
+
|
|
64
|
+
## Known Limitations and Deferred Work
|
|
65
|
+
|
|
66
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
67
|
+
|
|
68
|
+
- Only complete-recording transcription is supported. Streaming recognition and speech synthesis have no service methods.
|
|
69
|
+
|
|
70
|
+
-----
|
|
71
|
+
|
|
72
|
+
<a id="dev-note"></a>
|
|
73
|
+
### Dev Note
|
|
74
|
+
|
|
75
|
+
<details>
|
|
76
|
+
<summary>Maintainer details — click to expand</summary>
|
|
77
|
+
|
|
78
|
+
None.
|
|
79
|
+
|
|
80
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "具名实验性转写 Provider 及取消操作的所有权。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-experimental-speech-to-text
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
此服务定义通过 `ctx.speechToText` 选择具名语音识别器。消费者先解析请求再执行,Provider 独立注册。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用此包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与后续工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用此包
|
|
27
|
+
|
|
28
|
+
通过[语音输入 Bundle](../voice-input-bundle/README.zh.md)加载,或组合此服务、Provider 与消费者。`defaultProvider` 为必填项,选择确切的注册 id;Bundle 提供 `sensevoice-local`。`language` 提供省略的语言提示。缺失或重复的 Provider 会明确报错。
|
|
29
|
+
|
|
30
|
+
-----
|
|
31
|
+
|
|
32
|
+
<a id="understand-the-implementation"></a>
|
|
33
|
+
## 理解实现
|
|
34
|
+
|
|
35
|
+
<details>
|
|
36
|
+
<summary>维护者信息 — 点击展开</summary>
|
|
37
|
+
|
|
38
|
+
`resolve()` 捕获 Provider 实例、录音和语言。`transcribe()` 拒绝已撤销或替换的注册。注册的清理函数先拒绝新请求,再取消已接收的请求并等待 Provider 完成;Provider 必须响应取消。服务不会回退到其他识别器或上传音频。不发布运行时不变量伴随模块,因为注册表是 Provider 与准备状态观测的唯一来源。
|
|
39
|
+
|
|
40
|
+
`defaultProvider` 与 `language` 是 volatile Config 字段:`configure()` 通过 `settings` 服务把传入的字段写入本插件的 profile 条目,运行中的实例无需重挂载即可读到更新值;保存覆盖值之前使用组合配置默认值。没有 `settings` 或 profile 条目时 `configure()` 失败,并在保存前拒绝所选 Provider 不支持的语言。Provider 通过 `languages` 公布支持的语言提示;`resolve()` 在转写前校验所选提示。观察者通过完整 `SpeechSnapshot` 接收 Provider 拥有的准备状态;关闭观察者不会取消准备。
|
|
41
|
+
|
|
42
|
+
`./wave` 辅助函数为 Remote 消费者与原生识别进程校验规范的 16 kHz 单声道 PCM16 WAV。两者均在解码采样前拒绝不一致的头部和长度。
|
|
43
|
+
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
-----
|
|
47
|
+
|
|
48
|
+
<a id="further-exploration"></a>
|
|
49
|
+
## 进一步探索
|
|
50
|
+
|
|
51
|
+
[语音输入子系统](../../../docs/subsystems/voice-input.zh.md)
|
|
52
|
+
|
|
53
|
+
-----
|
|
54
|
+
|
|
55
|
+
<a id="model-experience"></a>
|
|
56
|
+
## 模型体验
|
|
57
|
+
|
|
58
|
+
无,因为录音与准备不进入模型请求;之后的文字由普通用户提交拥有。
|
|
59
|
+
|
|
60
|
+
#### KV 缓存影响
|
|
61
|
+
|
|
62
|
+
没有直接影响;普通提交拥有消息内容。
|
|
63
|
+
|
|
64
|
+
## 已知限制与后续工作
|
|
65
|
+
|
|
66
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
67
|
+
|
|
68
|
+
- 仅支持完整录音转写。服务没有流式识别或语音合成方法。
|
|
69
|
+
|
|
70
|
+
-----
|
|
71
|
+
|
|
72
|
+
<a id="dev-note"></a>
|
|
73
|
+
### 开发备注
|
|
74
|
+
|
|
75
|
+
<details>
|
|
76
|
+
<summary>维护者信息 — 点击展开</summary>
|
|
77
|
+
|
|
78
|
+
无。
|
|
79
|
+
|
|
80
|
+
</details>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
//#region lib/types/index.js
|
|
4
|
+
/** Named transcription providers with disposable registration and explicit routing. */
|
|
5
|
+
/** Registry shared by all transcription consumers in one Host composition. */
|
|
6
|
+
var SpeechToText = class extends Service {
|
|
7
|
+
config;
|
|
8
|
+
static Config = z.object({
|
|
9
|
+
defaultProvider: z.string().min(1).required().volatile(),
|
|
10
|
+
language: z.string().min(1).default("auto").volatile()
|
|
11
|
+
});
|
|
12
|
+
providers = /* @__PURE__ */ new Map();
|
|
13
|
+
listeners = /* @__PURE__ */ new Set();
|
|
14
|
+
lifetime = new AbortController();
|
|
15
|
+
/** Profile entry `configure()` writes to; absent when the plugin was mounted without Loader. */
|
|
16
|
+
entryId;
|
|
17
|
+
constructor(ctx, config) {
|
|
18
|
+
super(ctx, "speechToText");
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.entryId = ctx.fiber.entry?.id;
|
|
21
|
+
ctx.on("loader/volatile-update", () => {
|
|
22
|
+
this.changed();
|
|
23
|
+
});
|
|
24
|
+
ctx.effect(() => async () => {
|
|
25
|
+
this.lifetime.abort(/* @__PURE__ */ new Error("Speech service disposed"));
|
|
26
|
+
await Promise.all([...this.providers.values()].map((registration) => this.remove(registration)));
|
|
27
|
+
this.listeners.clear();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register one recognizer; duplicate ids fail without replacing the original.
|
|
32
|
+
* @param provider - recognizer owned by the contributing fiber.
|
|
33
|
+
* @returns idempotent disposer which rejects admission, cancels, and joins accepted work.
|
|
34
|
+
*/
|
|
35
|
+
register(provider) {
|
|
36
|
+
this.lifetime.signal.throwIfAborted();
|
|
37
|
+
if (this.providers.has(provider.info.id)) throw new Error(`Speech provider already registered: ${provider.info.id}`);
|
|
38
|
+
const registration = {
|
|
39
|
+
provider,
|
|
40
|
+
lifetime: new AbortController(),
|
|
41
|
+
pending: /* @__PURE__ */ new Set(),
|
|
42
|
+
unsubscribe: provider.preparation?.subscribe(() => {
|
|
43
|
+
this.changed();
|
|
44
|
+
}) ?? (() => {})
|
|
45
|
+
};
|
|
46
|
+
this.providers.set(provider.info.id, registration);
|
|
47
|
+
this.changed();
|
|
48
|
+
return async () => {
|
|
49
|
+
await this.remove(registration);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async remove(registration) {
|
|
53
|
+
if (this.providers.get(registration.provider.info.id) !== registration) return;
|
|
54
|
+
this.providers.delete(registration.provider.info.id);
|
|
55
|
+
registration.unsubscribe();
|
|
56
|
+
this.changed();
|
|
57
|
+
registration.lifetime.abort(/* @__PURE__ */ new Error("Speech provider unloaded"));
|
|
58
|
+
await Promise.allSettled(registration.pending);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Read the current recognizer roster.
|
|
62
|
+
* @returns available provider facts in registration order.
|
|
63
|
+
*/
|
|
64
|
+
listProviders() {
|
|
65
|
+
return [...this.providers.values()].map(({ provider }) => provider.info);
|
|
66
|
+
}
|
|
67
|
+
changed() {
|
|
68
|
+
for (const listener of this.listeners) listener();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Observe complete readiness snapshots; a slow reader coalesces intermediate progress.
|
|
72
|
+
* @param caller - observer lifetime, independent of any preparation task.
|
|
73
|
+
* @returns an initial snapshot followed by the latest provider states.
|
|
74
|
+
*/
|
|
75
|
+
async *follow(caller) {
|
|
76
|
+
const signal = AbortSignal.any([caller, this.lifetime.signal]);
|
|
77
|
+
signal.throwIfAborted();
|
|
78
|
+
let wake = Promise.withResolvers();
|
|
79
|
+
let changed = true;
|
|
80
|
+
const aborted = () => signal.aborted;
|
|
81
|
+
const notify = () => {
|
|
82
|
+
changed = true;
|
|
83
|
+
wake.resolve(void 0);
|
|
84
|
+
};
|
|
85
|
+
this.listeners.add(notify);
|
|
86
|
+
signal.addEventListener("abort", notify, { once: true });
|
|
87
|
+
try {
|
|
88
|
+
while (!aborted()) {
|
|
89
|
+
if (!changed) await wake.promise;
|
|
90
|
+
if (aborted()) break;
|
|
91
|
+
wake = Promise.withResolvers();
|
|
92
|
+
changed = false;
|
|
93
|
+
yield this.snapshot();
|
|
94
|
+
}
|
|
95
|
+
} finally {
|
|
96
|
+
this.listeners.delete(notify);
|
|
97
|
+
signal.removeEventListener("abort", notify);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Read provider readiness and current user preferences together.
|
|
102
|
+
* @returns one detached complete observation.
|
|
103
|
+
*/
|
|
104
|
+
snapshot() {
|
|
105
|
+
return {
|
|
106
|
+
providers: [...this.providers.values()].map(({ provider }) => ({
|
|
107
|
+
...provider.info,
|
|
108
|
+
preparation: provider.preparation?.snapshot() ?? { phase: "ready" }
|
|
109
|
+
})),
|
|
110
|
+
selection: {
|
|
111
|
+
providerId: this.config.defaultProvider.get(),
|
|
112
|
+
language: this.config.language.get()
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Persist changed selection fields into this plugin's profile entry; the resulting language must be accepted by the selected provider.
|
|
118
|
+
* @param patch - explicit provider or language changes.
|
|
119
|
+
* @returns after the profile write and the live update it applies.
|
|
120
|
+
*/
|
|
121
|
+
async configure(patch) {
|
|
122
|
+
const settings = this.ctx.get("settings");
|
|
123
|
+
const entry = this.entryId;
|
|
124
|
+
if (settings === void 0 || entry === void 0) throw new Error("Speech selection requires the settings service and a profile entry");
|
|
125
|
+
const id = patch.providerId ?? this.config.defaultProvider.get();
|
|
126
|
+
this.selectedProvider(id, patch.language ?? this.config.language.get());
|
|
127
|
+
await settings.update(entry, {
|
|
128
|
+
...patch.providerId === void 0 ? {} : { defaultProvider: patch.providerId },
|
|
129
|
+
...patch.language === void 0 ? {} : { language: patch.language }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
selectedProvider(id, language) {
|
|
133
|
+
const registration = this.providers.get(id);
|
|
134
|
+
if (!registration) throw new Error(`Speech provider is unavailable: ${id}`);
|
|
135
|
+
if (!registration.provider.info.languages.includes(language)) throw new Error(`Speech provider ${id} does not support language: ${language}`);
|
|
136
|
+
return registration.provider;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Start or join provider-owned preparation.
|
|
140
|
+
* @param id - exact registered provider identity.
|
|
141
|
+
*/
|
|
142
|
+
prepare(id) {
|
|
143
|
+
const registration = this.providers.get(id);
|
|
144
|
+
if (!registration) throw new Error(`Speech provider is unavailable: ${id}`);
|
|
145
|
+
registration.provider.preparation?.prepare();
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Explicitly cancel provider preparation without tying it to a browser connection.
|
|
149
|
+
* @param id - exact registered provider identity.
|
|
150
|
+
* @returns after the preparation task settles.
|
|
151
|
+
*/
|
|
152
|
+
async cancelPreparation(id) {
|
|
153
|
+
const registration = this.providers.get(id);
|
|
154
|
+
if (!registration) throw new Error(`Speech provider is unavailable: ${id}`);
|
|
155
|
+
await registration.provider.preparation?.cancel();
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Apply composition defaults and capture the selected provider. Missing providers and unsupported languages fail explicitly.
|
|
159
|
+
* @param request - complete recording and optional selection.
|
|
160
|
+
* @returns provider-pinned input for transcribe().
|
|
161
|
+
*/
|
|
162
|
+
resolve(request) {
|
|
163
|
+
const id = request.providerId ?? this.config.defaultProvider.get();
|
|
164
|
+
const language = request.language ?? this.config.language.get();
|
|
165
|
+
return {
|
|
166
|
+
provider: this.selectedProvider(id, language),
|
|
167
|
+
audio: request.audio,
|
|
168
|
+
language
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Execute exactly the resolved provider; no fallback sends audio elsewhere.
|
|
173
|
+
* @param spec - resolved input; a withdrawn or replaced registration is rejected.
|
|
174
|
+
* @param signal - caller cancellation.
|
|
175
|
+
* @returns final transcript after provider settlement.
|
|
176
|
+
*/
|
|
177
|
+
async transcribe(spec, signal) {
|
|
178
|
+
signal.throwIfAborted();
|
|
179
|
+
const registration = this.providers.get(spec.provider.info.id);
|
|
180
|
+
if (registration?.provider !== spec.provider) throw new Error("Resolved speech provider is no longer registered");
|
|
181
|
+
const combined = AbortSignal.any([signal, registration.lifetime.signal]);
|
|
182
|
+
const pending = Promise.resolve().then(() => {
|
|
183
|
+
combined.throwIfAborted();
|
|
184
|
+
return spec.provider.transcribe({
|
|
185
|
+
audio: spec.audio,
|
|
186
|
+
language: spec.language
|
|
187
|
+
}, combined);
|
|
188
|
+
});
|
|
189
|
+
registration.pending.add(pending);
|
|
190
|
+
try {
|
|
191
|
+
const result = await pending;
|
|
192
|
+
combined.throwIfAborted();
|
|
193
|
+
return result;
|
|
194
|
+
} finally {
|
|
195
|
+
registration.pending.delete(pending);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
//#endregion
|
|
200
|
+
export { SpeechToText as default };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Named transcription providers with disposable registration and explicit routing. */
|
|
2
|
+
import { Context, Service, type Volatile } from '@deepseek-ai/cordis';
|
|
3
|
+
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
import type { SpeechProvider, SpeechProviderId, SpeechProviderInfo, SpeechSnapshot, SpeechSelectionPatch, SpeechRequest, SpeechSpec, Transcript } from './types.ts';
|
|
5
|
+
export type * from './types.ts';
|
|
6
|
+
declare module '@deepseek-ai/cordis' {
|
|
7
|
+
interface Context {
|
|
8
|
+
/** Experimental speech recognition provider registry. */
|
|
9
|
+
speechToText: SpeechToText;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Live selection read before a transcription starts; `configure()` writes it through the profile. */
|
|
13
|
+
export interface Config {
|
|
14
|
+
/** Registered provider selected when the caller omits an id. */
|
|
15
|
+
defaultProvider: Volatile<string>;
|
|
16
|
+
/** Provider language hint selected when the caller omits one. */
|
|
17
|
+
language: Volatile<string>;
|
|
18
|
+
}
|
|
19
|
+
/** Registry shared by all transcription consumers in one Host composition. */
|
|
20
|
+
export default class SpeechToText extends Service {
|
|
21
|
+
private readonly config;
|
|
22
|
+
static Config: z<Schemastery.ObjectS<NoInfer<{
|
|
23
|
+
defaultProvider: z<string, string, "volatile-defined">;
|
|
24
|
+
language: z<string, string, "volatile-defined">;
|
|
25
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
26
|
+
defaultProvider: z<string, string, "volatile-defined">;
|
|
27
|
+
language: z<string, string, "volatile-defined">;
|
|
28
|
+
}>>, "plain">;
|
|
29
|
+
private readonly providers;
|
|
30
|
+
private readonly listeners;
|
|
31
|
+
private readonly lifetime;
|
|
32
|
+
/** Profile entry `configure()` writes to; absent when the plugin was mounted without Loader. */
|
|
33
|
+
private readonly entryId;
|
|
34
|
+
constructor(ctx: Context, config: Config);
|
|
35
|
+
/**
|
|
36
|
+
* Register one recognizer; duplicate ids fail without replacing the original.
|
|
37
|
+
* @param provider - recognizer owned by the contributing fiber.
|
|
38
|
+
* @returns idempotent disposer which rejects admission, cancels, and joins accepted work.
|
|
39
|
+
*/
|
|
40
|
+
register(provider: SpeechProvider): () => Promise<void>;
|
|
41
|
+
private remove;
|
|
42
|
+
/**
|
|
43
|
+
* Read the current recognizer roster.
|
|
44
|
+
* @returns available provider facts in registration order.
|
|
45
|
+
*/
|
|
46
|
+
listProviders(): readonly SpeechProviderInfo[];
|
|
47
|
+
private changed;
|
|
48
|
+
/**
|
|
49
|
+
* Observe complete readiness snapshots; a slow reader coalesces intermediate progress.
|
|
50
|
+
* @param caller - observer lifetime, independent of any preparation task.
|
|
51
|
+
* @returns an initial snapshot followed by the latest provider states.
|
|
52
|
+
*/
|
|
53
|
+
follow(caller: AbortSignal): AsyncIterable<SpeechSnapshot>;
|
|
54
|
+
/**
|
|
55
|
+
* Read provider readiness and current user preferences together.
|
|
56
|
+
* @returns one detached complete observation.
|
|
57
|
+
*/
|
|
58
|
+
snapshot(): SpeechSnapshot;
|
|
59
|
+
/**
|
|
60
|
+
* Persist changed selection fields into this plugin's profile entry; the resulting language must be accepted by the selected provider.
|
|
61
|
+
* @param patch - explicit provider or language changes.
|
|
62
|
+
* @returns after the profile write and the live update it applies.
|
|
63
|
+
*/
|
|
64
|
+
configure(patch: SpeechSelectionPatch): Promise<void>;
|
|
65
|
+
private selectedProvider;
|
|
66
|
+
/**
|
|
67
|
+
* Start or join provider-owned preparation.
|
|
68
|
+
* @param id - exact registered provider identity.
|
|
69
|
+
*/
|
|
70
|
+
prepare(id: SpeechProviderId): void;
|
|
71
|
+
/**
|
|
72
|
+
* Explicitly cancel provider preparation without tying it to a browser connection.
|
|
73
|
+
* @param id - exact registered provider identity.
|
|
74
|
+
* @returns after the preparation task settles.
|
|
75
|
+
*/
|
|
76
|
+
cancelPreparation(id: SpeechProviderId): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Apply composition defaults and capture the selected provider. Missing providers and unsupported languages fail explicitly.
|
|
79
|
+
* @param request - complete recording and optional selection.
|
|
80
|
+
* @returns provider-pinned input for transcribe().
|
|
81
|
+
*/
|
|
82
|
+
resolve(request: SpeechRequest): SpeechSpec;
|
|
83
|
+
/**
|
|
84
|
+
* Execute exactly the resolved provider; no fallback sends audio elsewhere.
|
|
85
|
+
* @param spec - resolved input; a withdrawn or replaced registration is rejected.
|
|
86
|
+
* @param signal - caller cancellation.
|
|
87
|
+
* @returns final transcript after provider settlement.
|
|
88
|
+
*/
|
|
89
|
+
transcribe(spec: SpeechSpec, signal: AbortSignal): Promise<Transcript>;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** Named transcription providers with disposable registration and explicit routing. */
|
|
2
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
3
|
+
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
/** Registry shared by all transcription consumers in one Host composition. */
|
|
5
|
+
export default class SpeechToText extends Service {
|
|
6
|
+
config;
|
|
7
|
+
static Config = z.object({
|
|
8
|
+
defaultProvider: z.string().min(1).required().volatile(),
|
|
9
|
+
language: z.string().min(1).default('auto').volatile(),
|
|
10
|
+
});
|
|
11
|
+
providers = new Map();
|
|
12
|
+
listeners = new Set();
|
|
13
|
+
lifetime = new AbortController();
|
|
14
|
+
/** Profile entry `configure()` writes to; absent when the plugin was mounted without Loader. */
|
|
15
|
+
entryId;
|
|
16
|
+
constructor(ctx, config) {
|
|
17
|
+
super(ctx, 'speechToText');
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.entryId = ctx.fiber.entry?.id;
|
|
20
|
+
ctx.on('loader/volatile-update', () => { this.changed(); });
|
|
21
|
+
ctx.effect(() => async () => {
|
|
22
|
+
this.lifetime.abort(new Error('Speech service disposed'));
|
|
23
|
+
await Promise.all([...this.providers.values()].map(registration => this.remove(registration)));
|
|
24
|
+
this.listeners.clear();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register one recognizer; duplicate ids fail without replacing the original.
|
|
29
|
+
* @param provider - recognizer owned by the contributing fiber.
|
|
30
|
+
* @returns idempotent disposer which rejects admission, cancels, and joins accepted work.
|
|
31
|
+
*/
|
|
32
|
+
register(provider) {
|
|
33
|
+
this.lifetime.signal.throwIfAborted();
|
|
34
|
+
if (this.providers.has(provider.info.id))
|
|
35
|
+
throw new Error(`Speech provider already registered: ${provider.info.id}`);
|
|
36
|
+
const registration = { provider, lifetime: new AbortController(), pending: new Set(),
|
|
37
|
+
unsubscribe: provider.preparation?.subscribe(() => { this.changed(); }) ?? (() => { }) };
|
|
38
|
+
this.providers.set(provider.info.id, registration);
|
|
39
|
+
this.changed();
|
|
40
|
+
return async () => { await this.remove(registration); };
|
|
41
|
+
}
|
|
42
|
+
async remove(registration) {
|
|
43
|
+
if (this.providers.get(registration.provider.info.id) !== registration)
|
|
44
|
+
return;
|
|
45
|
+
this.providers.delete(registration.provider.info.id);
|
|
46
|
+
registration.unsubscribe();
|
|
47
|
+
this.changed();
|
|
48
|
+
registration.lifetime.abort(new Error('Speech provider unloaded'));
|
|
49
|
+
await Promise.allSettled(registration.pending);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read the current recognizer roster.
|
|
53
|
+
* @returns available provider facts in registration order.
|
|
54
|
+
*/
|
|
55
|
+
listProviders() {
|
|
56
|
+
return [...this.providers.values()].map(({ provider }) => provider.info);
|
|
57
|
+
}
|
|
58
|
+
changed() { for (const listener of this.listeners)
|
|
59
|
+
listener(); }
|
|
60
|
+
/**
|
|
61
|
+
* Observe complete readiness snapshots; a slow reader coalesces intermediate progress.
|
|
62
|
+
* @param caller - observer lifetime, independent of any preparation task.
|
|
63
|
+
* @returns an initial snapshot followed by the latest provider states.
|
|
64
|
+
*/
|
|
65
|
+
async *follow(caller) {
|
|
66
|
+
const signal = AbortSignal.any([caller, this.lifetime.signal]);
|
|
67
|
+
signal.throwIfAborted();
|
|
68
|
+
let wake = Promise.withResolvers();
|
|
69
|
+
let changed = true;
|
|
70
|
+
const aborted = () => signal.aborted;
|
|
71
|
+
const notify = () => { changed = true; wake.resolve(undefined); };
|
|
72
|
+
this.listeners.add(notify);
|
|
73
|
+
signal.addEventListener('abort', notify, { once: true });
|
|
74
|
+
try {
|
|
75
|
+
while (!aborted()) {
|
|
76
|
+
if (!changed)
|
|
77
|
+
await wake.promise;
|
|
78
|
+
if (aborted())
|
|
79
|
+
break;
|
|
80
|
+
wake = Promise.withResolvers();
|
|
81
|
+
changed = false;
|
|
82
|
+
yield this.snapshot();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
this.listeners.delete(notify);
|
|
87
|
+
signal.removeEventListener('abort', notify);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Read provider readiness and current user preferences together.
|
|
92
|
+
* @returns one detached complete observation.
|
|
93
|
+
*/
|
|
94
|
+
snapshot() {
|
|
95
|
+
return { providers: [...this.providers.values()].map(({ provider }) => ({ ...provider.info,
|
|
96
|
+
preparation: provider.preparation?.snapshot() ?? { phase: 'ready' },
|
|
97
|
+
})), selection: { providerId: this.config.defaultProvider.get(), language: this.config.language.get() } };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Persist changed selection fields into this plugin's profile entry; the resulting language must be accepted by the selected provider.
|
|
101
|
+
* @param patch - explicit provider or language changes.
|
|
102
|
+
* @returns after the profile write and the live update it applies.
|
|
103
|
+
*/
|
|
104
|
+
async configure(patch) {
|
|
105
|
+
const settings = this.ctx.get('settings');
|
|
106
|
+
const entry = this.entryId;
|
|
107
|
+
if (settings === undefined || entry === undefined)
|
|
108
|
+
throw new Error('Speech selection requires the settings service and a profile entry');
|
|
109
|
+
const id = patch.providerId ?? this.config.defaultProvider.get();
|
|
110
|
+
this.selectedProvider(id, patch.language ?? this.config.language.get());
|
|
111
|
+
await settings.update(entry, { ...patch.providerId === undefined ? {} : { defaultProvider: patch.providerId },
|
|
112
|
+
...patch.language === undefined ? {} : { language: patch.language } });
|
|
113
|
+
}
|
|
114
|
+
selectedProvider(id, language) {
|
|
115
|
+
const registration = this.providers.get(id);
|
|
116
|
+
if (!registration)
|
|
117
|
+
throw new Error(`Speech provider is unavailable: ${id}`);
|
|
118
|
+
if (!registration.provider.info.languages.includes(language)) {
|
|
119
|
+
throw new Error(`Speech provider ${id} does not support language: ${language}`);
|
|
120
|
+
}
|
|
121
|
+
return registration.provider;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Start or join provider-owned preparation.
|
|
125
|
+
* @param id - exact registered provider identity.
|
|
126
|
+
*/
|
|
127
|
+
prepare(id) {
|
|
128
|
+
const registration = this.providers.get(id);
|
|
129
|
+
if (!registration)
|
|
130
|
+
throw new Error(`Speech provider is unavailable: ${id}`);
|
|
131
|
+
registration.provider.preparation?.prepare();
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Explicitly cancel provider preparation without tying it to a browser connection.
|
|
135
|
+
* @param id - exact registered provider identity.
|
|
136
|
+
* @returns after the preparation task settles.
|
|
137
|
+
*/
|
|
138
|
+
async cancelPreparation(id) {
|
|
139
|
+
const registration = this.providers.get(id);
|
|
140
|
+
if (!registration)
|
|
141
|
+
throw new Error(`Speech provider is unavailable: ${id}`);
|
|
142
|
+
await registration.provider.preparation?.cancel();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Apply composition defaults and capture the selected provider. Missing providers and unsupported languages fail explicitly.
|
|
146
|
+
* @param request - complete recording and optional selection.
|
|
147
|
+
* @returns provider-pinned input for transcribe().
|
|
148
|
+
*/
|
|
149
|
+
resolve(request) {
|
|
150
|
+
const id = request.providerId ?? this.config.defaultProvider.get();
|
|
151
|
+
const language = request.language ?? this.config.language.get();
|
|
152
|
+
return { provider: this.selectedProvider(id, language), audio: request.audio, language };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Execute exactly the resolved provider; no fallback sends audio elsewhere.
|
|
156
|
+
* @param spec - resolved input; a withdrawn or replaced registration is rejected.
|
|
157
|
+
* @param signal - caller cancellation.
|
|
158
|
+
* @returns final transcript after provider settlement.
|
|
159
|
+
*/
|
|
160
|
+
async transcribe(spec, signal) {
|
|
161
|
+
signal.throwIfAborted();
|
|
162
|
+
const registration = this.providers.get(spec.provider.info.id);
|
|
163
|
+
if (registration?.provider !== spec.provider)
|
|
164
|
+
throw new Error('Resolved speech provider is no longer registered');
|
|
165
|
+
const combined = AbortSignal.any([signal, registration.lifetime.signal]);
|
|
166
|
+
const pending = Promise.resolve().then(() => {
|
|
167
|
+
combined.throwIfAborted();
|
|
168
|
+
return spec.provider.transcribe({ audio: spec.audio, language: spec.language }, combined);
|
|
169
|
+
});
|
|
170
|
+
registration.pending.add(pending);
|
|
171
|
+
try {
|
|
172
|
+
const result = await pending;
|
|
173
|
+
combined.throwIfAborted();
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
registration.pending.delete(pending);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** Provider-neutral speech transcription inputs and registration metadata. */
|
|
2
|
+
import type { Branded } from '@deepseek-ai/dsh-brand';
|
|
3
|
+
/** Configured identity of one transcription provider. */
|
|
4
|
+
export type SpeechProviderId = Branded<'SpeechProviderId'>;
|
|
5
|
+
/** Provider-specific planning estimates, not measured progress or guaranteed resource limits. */
|
|
6
|
+
export interface SpeechSetupEstimate {
|
|
7
|
+
readonly recommendedDiskBytes: number;
|
|
8
|
+
readonly expectedMemoryBytes: number;
|
|
9
|
+
readonly minimumMinutes: number;
|
|
10
|
+
readonly maximumMinutes: number;
|
|
11
|
+
}
|
|
12
|
+
/** Public provider facts; credentials and filesystem paths are excluded. */
|
|
13
|
+
export interface SpeechProviderInfo {
|
|
14
|
+
readonly id: SpeechProviderId;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly location: 'host-local' | 'cloud';
|
|
17
|
+
/** Accepted language hints, including automatic detection when supported. */
|
|
18
|
+
readonly languages: readonly string[];
|
|
19
|
+
readonly setupEstimate?: SpeechSetupEstimate;
|
|
20
|
+
}
|
|
21
|
+
/** Ordered resource-preparation operations understood by the speech UI. Providers omit operations they do not need. */
|
|
22
|
+
export type SpeechPreparationStepKind = 'check' | 'model' | 'vad' | 'verify' | 'load';
|
|
23
|
+
/** One Host-owned operation; only its running state has a live elapsed clock. */
|
|
24
|
+
export interface SpeechPreparationStep {
|
|
25
|
+
readonly kind: SpeechPreparationStepKind;
|
|
26
|
+
readonly status: 'pending' | 'running' | 'complete' | 'failed' | 'cancelled';
|
|
27
|
+
readonly startedAt?: number;
|
|
28
|
+
}
|
|
29
|
+
/** Safe download diagnostics for localized preparation guidance; URLs omit credentials and query strings. */
|
|
30
|
+
export interface SpeechDownloadFailure {
|
|
31
|
+
readonly resource: string;
|
|
32
|
+
readonly source: string;
|
|
33
|
+
readonly reason: 'network' | 'dns' | 'timeout' | 'certificate' | 'http' | 'integrity' | 'storage' | 'unknown';
|
|
34
|
+
readonly code?: string;
|
|
35
|
+
readonly status?: number;
|
|
36
|
+
}
|
|
37
|
+
/** Host-owned preparation state; byte totals describe downloads, never estimated installation percentages. */
|
|
38
|
+
export type SpeechPreparationState = ({
|
|
39
|
+
readonly phase: 'unprepared' | 'ready' | 'standby' | 'cancelled';
|
|
40
|
+
} | {
|
|
41
|
+
readonly phase: 'downloading';
|
|
42
|
+
readonly resource: string;
|
|
43
|
+
readonly completedBytes: number;
|
|
44
|
+
readonly totalBytes?: number;
|
|
45
|
+
} | {
|
|
46
|
+
readonly phase: 'checking' | 'loading' | 'waking' | 'cancelling';
|
|
47
|
+
readonly startedAt: number;
|
|
48
|
+
} | {
|
|
49
|
+
readonly phase: 'failed';
|
|
50
|
+
readonly message: string;
|
|
51
|
+
readonly download?: SpeechDownloadFailure;
|
|
52
|
+
}) & {
|
|
53
|
+
readonly step?: SpeechPreparationStepKind;
|
|
54
|
+
readonly steps?: readonly SpeechPreparationStep[];
|
|
55
|
+
};
|
|
56
|
+
/** Provider facts and its current resource readiness; independent of plugin activation. */
|
|
57
|
+
export interface SpeechProviderView extends SpeechProviderInfo {
|
|
58
|
+
readonly preparation: SpeechPreparationState;
|
|
59
|
+
}
|
|
60
|
+
/** Host-persisted default selection captured when a recording starts. */
|
|
61
|
+
export interface SpeechSelection {
|
|
62
|
+
readonly providerId: SpeechProviderId;
|
|
63
|
+
readonly language: string;
|
|
64
|
+
}
|
|
65
|
+
/** User intent updates only the preference fields explicitly changed. */
|
|
66
|
+
export interface SpeechSelectionPatch {
|
|
67
|
+
readonly providerId?: SpeechProviderId;
|
|
68
|
+
readonly language?: string;
|
|
69
|
+
}
|
|
70
|
+
/** One consistent provider roster and default-selection observation. */
|
|
71
|
+
export interface SpeechSnapshot {
|
|
72
|
+
readonly providers: readonly SpeechProviderView[];
|
|
73
|
+
readonly selection: SpeechSelection;
|
|
74
|
+
}
|
|
75
|
+
/** Optional resource preparation controls. Providers without them are immediately usable. */
|
|
76
|
+
export interface SpeechPreparation {
|
|
77
|
+
/** @returns the latest Host-owned state. */
|
|
78
|
+
snapshot(): SpeechPreparationState;
|
|
79
|
+
/** @param listener - state invalidation callback. @returns subscription disposer. */
|
|
80
|
+
subscribe(listener: () => void): () => void;
|
|
81
|
+
/** Start or join the current preparation task; page and transport lifetimes do not own it. */
|
|
82
|
+
prepare(): void;
|
|
83
|
+
/** Cancel preparation. @returns after its resources settle. */
|
|
84
|
+
cancel(): Promise<void>;
|
|
85
|
+
}
|
|
86
|
+
/** Complete audio recording and an explicit language hint. */
|
|
87
|
+
export interface SpeechInput {
|
|
88
|
+
readonly audio: Uint8Array;
|
|
89
|
+
readonly language: string;
|
|
90
|
+
}
|
|
91
|
+
/** Final transcription; an empty string means no speech was recognized. */
|
|
92
|
+
export interface Transcript {
|
|
93
|
+
readonly text: string;
|
|
94
|
+
readonly audioSeconds: number;
|
|
95
|
+
readonly inferenceSeconds: number;
|
|
96
|
+
}
|
|
97
|
+
/** One replaceable recognizer. It owns preparation, execution, and cancellation. */
|
|
98
|
+
export interface SpeechProvider {
|
|
99
|
+
readonly info: SpeechProviderInfo;
|
|
100
|
+
readonly preparation?: SpeechPreparation;
|
|
101
|
+
/**
|
|
102
|
+
* Recognize one complete recording without submitting an Agent message.
|
|
103
|
+
* @param input - WAV bytes and language, borrowed until settlement.
|
|
104
|
+
* @param signal - caller or registration cancellation; rejection follows resource cleanup.
|
|
105
|
+
* @returns final text and measured audio/inference durations.
|
|
106
|
+
*/
|
|
107
|
+
transcribe(input: SpeechInput, signal: AbortSignal): Promise<Transcript>;
|
|
108
|
+
}
|
|
109
|
+
/** Caller selection before composition defaults are resolved. */
|
|
110
|
+
export interface SpeechRequest {
|
|
111
|
+
readonly audio: Uint8Array;
|
|
112
|
+
readonly providerId?: SpeechProviderId;
|
|
113
|
+
readonly language?: string;
|
|
114
|
+
}
|
|
115
|
+
/** Resolved selection pins the exact registered provider, including its lifetime. */
|
|
116
|
+
export interface SpeechSpec extends SpeechInput {
|
|
117
|
+
readonly provider: SpeechProvider;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Validate browser PCM WAV headers before retaining or forwarding audio. */
|
|
2
|
+
/**
|
|
3
|
+
* Read a canonical 16 kHz mono PCM16 WAV recording, rejecting inconsistent lengths.
|
|
4
|
+
* @param audio - decoded wire bytes.
|
|
5
|
+
* @param maxDurationSeconds - maximum admitted recording duration.
|
|
6
|
+
* @returns complete recording duration in seconds.
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateWave(audio: Uint8Array, maxDurationSeconds: number): number;
|
|
9
|
+
//# sourceMappingURL=wave.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Validate browser PCM WAV headers before retaining or forwarding audio. */
|
|
2
|
+
/**
|
|
3
|
+
* Read a canonical 16 kHz mono PCM16 WAV recording, rejecting inconsistent lengths.
|
|
4
|
+
* @param audio - decoded wire bytes.
|
|
5
|
+
* @param maxDurationSeconds - maximum admitted recording duration.
|
|
6
|
+
* @returns complete recording duration in seconds.
|
|
7
|
+
*/
|
|
8
|
+
export function validateWave(audio, maxDurationSeconds) {
|
|
9
|
+
const data = Buffer.from(audio.buffer, audio.byteOffset, audio.byteLength);
|
|
10
|
+
if (data.length < 46 || data.toString('ascii', 0, 4) !== 'RIFF'
|
|
11
|
+
|| data.toString('ascii', 8, 12) !== 'WAVE' || data.toString('ascii', 12, 16) !== 'fmt '
|
|
12
|
+
|| data.readUInt32LE(16) !== 16 || data.readUInt16LE(20) !== 1 || data.readUInt16LE(22) !== 1
|
|
13
|
+
|| data.readUInt32LE(24) !== 16000 || data.readUInt32LE(28) !== 32000
|
|
14
|
+
|| data.readUInt16LE(32) !== 2 || data.readUInt16LE(34) !== 16
|
|
15
|
+
|| data.toString('ascii', 36, 40) !== 'data' || data.readUInt32LE(4) !== data.length - 8
|
|
16
|
+
|| data.readUInt32LE(40) !== data.length - 44 || (data.length - 44) % 2 !== 0) {
|
|
17
|
+
throw new Error('Audio must be a canonical 16 kHz mono PCM16 WAV recording');
|
|
18
|
+
}
|
|
19
|
+
const seconds = (data.length - 44) / 32000;
|
|
20
|
+
if (seconds > maxDurationSeconds)
|
|
21
|
+
throw new Error(`Audio exceeds ${maxDurationSeconds} seconds`);
|
|
22
|
+
return seconds;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=wave.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-experimental-speech-to-text",
|
|
3
|
+
"description": "Experimental speech recognition with independently selectable providers",
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/experimental/speech-to-text"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./src/*": "./src/*",
|
|
22
|
+
"./package.json": "./package.json",
|
|
23
|
+
"./types": {
|
|
24
|
+
"types": "./lib/types/types.d.ts",
|
|
25
|
+
"default": "./lib/types/types.js"
|
|
26
|
+
},
|
|
27
|
+
"./wave": {
|
|
28
|
+
"types": "./lib/types/wave.d.ts",
|
|
29
|
+
"default": "./lib/types/wave.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"lib/index.js",
|
|
34
|
+
"lib/types/**/*.js",
|
|
35
|
+
"lib/types/**/*.d.ts"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@deepseek-ai/dsh-brand": "^0.1.7-alpha.1",
|
|
40
|
+
"@deepseek-ai/schemastery": "^3.18.3"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-alpha.1",
|
|
44
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.4",
|
|
45
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
49
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-alpha.1",
|
|
50
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.4"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"@deepseek-ai/cordis-plugin-loader": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|