@bittersmilezzz/dsh-asr-voice 0.3.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +325 -0
  3. package/cordis.patch.yml +32 -0
  4. package/lib/asr-models.js +83 -0
  5. package/lib/asr-models.js.map +1 -0
  6. package/lib/client.js +6706 -0
  7. package/lib/client.js.map +1 -0
  8. package/lib/http.js +113 -0
  9. package/lib/http.js.map +1 -0
  10. package/lib/index.js +193 -0
  11. package/lib/index.js.map +1 -0
  12. package/lib/key-ref.js +25 -0
  13. package/lib/key-ref.js.map +1 -0
  14. package/lib/optimize.js +168 -0
  15. package/lib/optimize.js.map +1 -0
  16. package/lib/presets.js +98 -0
  17. package/lib/presets.js.map +1 -0
  18. package/lib/realtime-dashscope.js +211 -0
  19. package/lib/realtime-dashscope.js.map +1 -0
  20. package/lib/realtime-host.js +413 -0
  21. package/lib/realtime-host.js.map +1 -0
  22. package/lib/realtime-provider.js +143 -0
  23. package/lib/realtime-provider.js.map +1 -0
  24. package/lib/realtime-tts.js +179 -0
  25. package/lib/realtime-tts.js.map +1 -0
  26. package/lib/settings.js +155 -0
  27. package/lib/settings.js.map +1 -0
  28. package/lib/stats.js +30 -0
  29. package/lib/stats.js.map +1 -0
  30. package/lib/transcribe.js +274 -0
  31. package/lib/transcribe.js.map +1 -0
  32. package/lib/types/asr-models.d.ts +30 -0
  33. package/lib/types/asr-models.d.ts.map +1 -0
  34. package/lib/types/http.d.ts +41 -0
  35. package/lib/types/http.d.ts.map +1 -0
  36. package/lib/types/index.d.ts +75 -0
  37. package/lib/types/index.d.ts.map +1 -0
  38. package/lib/types/key-ref.d.ts +9 -0
  39. package/lib/types/key-ref.d.ts.map +1 -0
  40. package/lib/types/optimize.d.ts +41 -0
  41. package/lib/types/optimize.d.ts.map +1 -0
  42. package/lib/types/presets.d.ts +64 -0
  43. package/lib/types/presets.d.ts.map +1 -0
  44. package/lib/types/realtime-dashscope.d.ts +41 -0
  45. package/lib/types/realtime-dashscope.d.ts.map +1 -0
  46. package/lib/types/realtime-host.d.ts +95 -0
  47. package/lib/types/realtime-host.d.ts.map +1 -0
  48. package/lib/types/realtime-provider.d.ts +76 -0
  49. package/lib/types/realtime-provider.d.ts.map +1 -0
  50. package/lib/types/realtime-tts.d.ts +42 -0
  51. package/lib/types/realtime-tts.d.ts.map +1 -0
  52. package/lib/types/settings.d.ts +143 -0
  53. package/lib/types/settings.d.ts.map +1 -0
  54. package/lib/types/stats.d.ts +29 -0
  55. package/lib/types/stats.d.ts.map +1 -0
  56. package/lib/types/transcribe.d.ts +46 -0
  57. package/lib/types/transcribe.d.ts.map +1 -0
  58. package/lib/types/tsconfig.client.tsbuildinfo +1 -0
  59. package/package.json +146 -0
@@ -0,0 +1,274 @@
1
+ import { mkdir, readdir, unlink, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { CHAT_COMPLETIONS_PATH, MAX_AUDIO_BYTES, TRANSCRIBE_PATH, resolveAsrMode } from "./presets.js";
5
+ import { keyRefFor } from "./key-ref.js";
6
+ import { guardRoute, readRawBody, sendJson, statusOfBodyError } from "./http.js";
7
+ /** 上游 ASR 请求超时(ms):上游不可达/卡死时不无限挂起请求。 */
8
+ const UPSTREAM_TIMEOUT_MS = 60_000;
9
+ /** 模型名 → 上游语言参数(DSH 配置是 auto/zh-CN/en-US,上游要 zh/en)。 */
10
+ function upstreamLanguage(language) {
11
+ if (!language || language === 'auto')
12
+ return undefined;
13
+ const map = { 'zh-CN': 'zh', 'zh': 'zh', 'en-US': 'en', 'en': 'en' };
14
+ return map[language] ?? language;
15
+ }
16
+ /** 从响应里抽错误原因(兼容 { error: string|{message} } 与 { message } 两种形状)。 */
17
+ function errorReason(data, status) {
18
+ if (typeof data.error === 'string' && data.error !== '')
19
+ return data.error;
20
+ if (typeof data.error === 'object' && data.error !== null) {
21
+ const msg = data.error.message;
22
+ if (typeof msg === 'string' && msg !== '')
23
+ return msg;
24
+ }
25
+ if (typeof data.message === 'string' && data.message !== '')
26
+ return data.message;
27
+ return `upstream ASR failed (HTTP ${status})`;
28
+ }
29
+ /** 调试落盘目录:保存浏览器发来的原始音频,便于重放定位(仅诊断用)。 */
30
+ function debugDir() {
31
+ const env = globalThis.process?.env?.DSH_ASR_DEBUG_DIR;
32
+ return env && env !== '' ? env : join(homedir(), '.dsh', 'asr-voice-debug');
33
+ }
34
+ /** 成功转写是否也落盘(诊断期用 DSH_ASR_DEBUG_KEEP_WAVS=1 dsh web 开启;默认只存失败样本)。 */
35
+ function keepAllWavs() {
36
+ const v = globalThis.process?.env?.DSH_ASR_DEBUG_KEEP_WAVS;
37
+ return v === '1' || v === 'true' || v === 'yes';
38
+ }
39
+ /** 从请求 UA 推断浏览器短名(落盘标签用,如 Chrome150/Edge/Safari18)。 */
40
+ function uaTag(req) {
41
+ const ua = String(req.headers['user-agent'] ?? '');
42
+ const ver = (re) => {
43
+ const m = ua.match(re);
44
+ return m?.[1] !== undefined ? m[1] : '';
45
+ };
46
+ if (/Edg\//.test(ua))
47
+ return `Edge${ver(/Edg\/(\d+)/)}`;
48
+ if (/Chrome\//.test(ua))
49
+ return `Chrome${ver(/Chrome\/(\d+)/)}`;
50
+ if (/Version\//.test(ua) && /Safari\//.test(ua))
51
+ return `Safari${ver(/Version\/(\d+)/)}`;
52
+ if (/Firefox\//.test(ua))
53
+ return `Firefox${ver(/Firefox\/(\d+)/)}`;
54
+ return 'UA';
55
+ }
56
+ /** 目录里最多保留的音频个数(超出删最旧,防长期诊断撑爆磁盘)。 */
57
+ const MAX_KEPT_FILES = 100;
58
+ /** 把原始音频落盘(fire-and-forget,绝不阻断路由)。 */
59
+ async function saveDebugAudio(audio, mime, tag) {
60
+ try {
61
+ const dir = debugDir();
62
+ await mkdir(dir, { recursive: true });
63
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
64
+ const safeTag = tag.replace(/[^a-z0-9]+/gi, '-').slice(0, 40) || 'unknown';
65
+ await writeFile(join(dir, `${stamp}-${audio.length}B-${safeTag}.${extForMime(mime)}`), audio);
66
+ const entries = await readdir(dir).catch(() => []);
67
+ if (entries.length > MAX_KEPT_FILES) {
68
+ for (const name of entries.sort().slice(0, entries.length - MAX_KEPT_FILES)) {
69
+ await unlink(join(dir, name)).catch(() => { });
70
+ }
71
+ }
72
+ }
73
+ catch {
74
+ // 诊断落盘失败不影响主流程
75
+ }
76
+ }
77
+ /** whisper 式 multipart /audio/transcriptions。 */
78
+ async function upstreamTranscribeMultipart(cfg, audio, mime, language, apiKey) {
79
+ const base = cfg.baseUrl.replace(/\/+$/, '');
80
+ const form = new FormData();
81
+ const ext = extForMime(mime);
82
+ // Buffer → fresh Uint8Array<ArrayBuffer> so it satisfies BlobPart under
83
+ // the DOM/undici typings (Node Buffer is Uint8Array<ArrayBufferLike>).
84
+ const bytes = new Uint8Array(audio.byteLength);
85
+ bytes.set(audio);
86
+ form.append('file', new Blob([bytes], { type: mime }), `recording.${ext}`);
87
+ const model = cfg.model || 'whisper-1';
88
+ form.append('model', model);
89
+ if (language && language !== 'auto')
90
+ form.append('language', language);
91
+ const res = await fetch(`${base}${TRANSCRIBE_PATH}`, {
92
+ method: 'POST',
93
+ headers: { Authorization: `Bearer ${apiKey}` },
94
+ body: form,
95
+ // 上游卡死时及时失败(客户端 60s 超时后 host 不应继续占着请求)。
96
+ signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS),
97
+ });
98
+ const data = (await res.json().catch(() => ({})));
99
+ if (!res.ok) {
100
+ throw new Error(errorReason(data, res.status));
101
+ }
102
+ if (typeof data.text !== 'string' || data.text.trim() === '') {
103
+ throw new Error('upstream ASR returned no text');
104
+ }
105
+ return { text: data.text };
106
+ }
107
+ /** chat-completions + input_audio(base64 data URI):MiMo / Qwen-ASR 通道。 */
108
+ async function upstreamTranscribeChat(cfg, audio, mime, language, apiKey) {
109
+ const base = cfg.baseUrl.replace(/\/+$/, '');
110
+ const model = cfg.model || 'mimo-v2.5-asr';
111
+ const dataUri = `data:${mime};base64,${audio.toString('base64')}`;
112
+ const lang = upstreamLanguage(language);
113
+ const payload = {
114
+ model,
115
+ messages: [
116
+ { role: 'user', content: [{ type: 'input_audio', input_audio: { data: dataUri } }] },
117
+ ],
118
+ };
119
+ if (lang !== undefined)
120
+ payload.asr_options = { language: lang };
121
+ const res = await fetch(`${base}${CHAT_COMPLETIONS_PATH}`, {
122
+ method: 'POST',
123
+ headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
124
+ body: JSON.stringify(payload),
125
+ signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS),
126
+ });
127
+ const data = (await res.json().catch(() => ({})));
128
+ if (!res.ok) {
129
+ throw new Error(errorReason(data, res.status));
130
+ }
131
+ // 兼容 content 为字符串或多模态数组([{type:'text',text:'…'}])两种形状。
132
+ let content = data.choices?.[0]?.message?.content;
133
+ if (Array.isArray(content)) {
134
+ // 循环拼接替代 map+join:避免为最常见的单元素数组分配中间数组。
135
+ let acc = '';
136
+ for (const p of content) {
137
+ const t = p.text;
138
+ if (typeof t === 'string')
139
+ acc += t;
140
+ }
141
+ content = acc;
142
+ }
143
+ if (typeof content !== 'string' || content.trim() === '') {
144
+ throw new Error(`upstream ASR returned no text (${audio.length}B ${mime})`);
145
+ }
146
+ // auto 语言模式会带 <chinese>/<english> 等标签,去掉。
147
+ // 仅在 auto(未显式指定语言)时清洗,且只匹配已知语言标签,
148
+ // 避免误删口述内容中的尖括号(如「用 <div> 包起来」)。
149
+ const text = (lang === undefined
150
+ ? content.replace(/<\/?(?:chinese|english|zh|en|ja|japanese|ko|korean)>/gi, '')
151
+ : content).trim();
152
+ if (text === '')
153
+ throw new Error(`upstream ASR returned no text (${audio.length}B ${mime})`);
154
+ return { text };
155
+ }
156
+ /** 按 mode 分发到对应通道。 */
157
+ async function upstreamTranscribe(cfg, audio, mime, language, apiKey) {
158
+ const mode = resolveAsrMode(cfg.mode, cfg.model || '');
159
+ if (mode === 'chat')
160
+ return upstreamTranscribeChat(cfg, audio, mime, language, apiKey);
161
+ return upstreamTranscribeMultipart(cfg, audio, mime, language, apiKey);
162
+ }
163
+ /** 解析最终 API key:settings 里的遗留值优先(一次性迁移完成前的兼容路径),否则按 派生引用名向 DSH credentials 服务解析,最后退回同名环境变量。预置供应商的引用名与 官方 LLM provider 同名,因此配过对应 LLM 的用户在这里天然命中同一把 key。
164
+ * 凭据服务 `resolve` 的契约是「未配置 → undefined」,**抛错 = 服务真故障**(后端不可用 /
165
+ * 权限拒绝),两者含义完全不同:早先空 catch 把故障吞成「返回空串」,用户看到的是
166
+ * "no API key" 提示,于是去设置页反复确认凭据明明存在,真实故障被彻底掩盖。现在把故障
167
+ * 原因留到「环境变量也没兜到 key」时抛出——那时真相就是「无法判断有没有 key」,比谎报
168
+ * 「没配 key」诚实。 */
169
+ export async function resolveApiKey(ctx, cfg) {
170
+ const legacy = cfg.apiKey.trim();
171
+ if (legacy !== '')
172
+ return legacy;
173
+ const ref = keyRefFor(cfg);
174
+ const credentials = ctx.get('credentials');
175
+ let lookupError;
176
+ if (credentials !== undefined) {
177
+ try {
178
+ const hit = await credentials.resolve(ref);
179
+ if (hit?.value)
180
+ return hit.value;
181
+ }
182
+ catch (error) {
183
+ lookupError = error;
184
+ }
185
+ }
186
+ // 兜底:环境变量(DSH 进程若以 export 方式注入也能读到)。
187
+ const globalProcess = globalThis.process;
188
+ const fromEnv = globalProcess?.env?.[ref] ?? '';
189
+ if (fromEnv !== '')
190
+ return fromEnv;
191
+ if (lookupError !== undefined) {
192
+ const cause = lookupError instanceof Error ? lookupError.message : String(lookupError);
193
+ throw new Error(`credential ${ref} lookup failed: ${cause} — cannot tell whether a key is configured`);
194
+ }
195
+ return '';
196
+ }
197
+ /** 根据 MIME 推断文件扩展名(用于 multipart filename;不影响转写)。 */
198
+ function extForMime(mime) {
199
+ const m = mime.split(';')[0]?.trim().toLowerCase() ?? '';
200
+ const map = {
201
+ 'audio/webm': 'webm',
202
+ 'audio/ogg': 'ogg',
203
+ 'audio/mp4': 'm4a',
204
+ 'audio/mpeg': 'mp3',
205
+ 'audio/wav': 'wav',
206
+ 'audio/x-wav': 'wav',
207
+ 'audio/x-m4a': 'm4a',
208
+ 'audio/aac': 'aac',
209
+ 'audio/flac': 'flac',
210
+ };
211
+ return map[m] ?? 'bin';
212
+ }
213
+ /** 注册 /api/asr-voice/transcribe 路由。
214
+ * @param register - webserver 的 register 方法(由调用方从 ctx 传入)。
215
+ * @param getCloudConfig - 读取当前生效云端 ASR 配置的 thunk。
216
+ * @param ctx - host context(供 MiMo key 兜底走 credentials 服务)。
217
+ * @param recordStats - 成功转写后回调(记录用量;可选)。
218
+ * @returns 路由 disposer(由 ctx.effect 挂载/回收)。
219
+ */
220
+ export function registerTranscribeRoute(register, getCloudConfig, ctx, recordStats) {
221
+ return register({
222
+ kind: 'exact',
223
+ path: '/api/asr-voice/transcribe',
224
+ handler: async (req, res) => {
225
+ const denied = guardRoute(req);
226
+ if (denied !== null)
227
+ return sendJson(res, denied.status, denied.payload);
228
+ let audio = Buffer.alloc(0);
229
+ let mime = 'audio/webm';
230
+ let ua = 'UA';
231
+ try {
232
+ audio = await readRawBody(req, MAX_AUDIO_BYTES);
233
+ if (audio.length === 0)
234
+ return sendJson(res, 400, { ok: false, reason: 'empty audio body' });
235
+ mime = String(req.headers['content-type'] ?? 'audio/webm').split(';')[0]?.trim() || 'audio/webm';
236
+ const url = new URL(req.url ?? '/', 'http://localhost');
237
+ // 诊断标记:从请求 UA 推断浏览器,让每个落盘文件的标签自带浏览器身份。
238
+ ua = uaTag(req);
239
+ // 纯抓取请求(诊断):保存转换前的原始录音后直接返回,不调用上游。
240
+ if (url.searchParams.get('capture') === '1') {
241
+ void saveDebugAudio(audio, mime, `raw-${ua}`);
242
+ return sendJson(res, 200, { ok: true, saved: true });
243
+ }
244
+ const language = url.searchParams.get('language') ?? undefined;
245
+ const cfg = getCloudConfig();
246
+ if (!cfg.baseUrl.trim()) {
247
+ return sendJson(res, 400, { ok: false, reason: 'cloud ASR not configured: set baseUrl in plugin settings' });
248
+ }
249
+ const apiKey = await resolveApiKey(ctx, cfg);
250
+ if (!apiKey) {
251
+ return sendJson(res, 400, { ok: false, reason: `no API key: set the credential ${keyRefFor(cfg)} in DSH (a same-named LLM key is reused automatically)` });
252
+ }
253
+ const { text } = await upstreamTranscribe(cfg, audio, mime, language, apiKey);
254
+ // 诊断抓取:成功音频在 DSH_ASR_DEBUG_KEEP_WAVS=1 时全存;识别结果过短
255
+ // (≤8 字符,覆盖 "yeah"/单个语气词 等疑似听错/幻觉)时总是落盘,便于重放定位。
256
+ if (keepAllWavs() || text.trim().length <= 8)
257
+ void saveDebugAudio(audio, mime, `${ua}-ok-${text.slice(0, 18)}`);
258
+ // 用量统计(可选,dev 功能)。
259
+ recordStats?.(text, cfg.id);
260
+ return sendJson(res, 200, { ok: true, text });
261
+ }
262
+ catch (error) {
263
+ const base = error instanceof Error ? error.message : String(error);
264
+ const reason = audio.length > 0 ? `${base} (audio ${audio.length}B, ${mime})` : base;
265
+ // 失败时把原始音频落盘到 ~/.dsh/asr-voice-debug/(重放定位用,不影响主流程)。
266
+ if (audio.length > 0)
267
+ void saveDebugAudio(audio, mime, `${ua}-${base}`);
268
+ // 超限/超时是输入问题(413/408),不该报成 502 把排查引向上游。
269
+ return sendJson(res, statusOfBodyError(error, 502), { ok: false, reason });
270
+ }
271
+ },
272
+ });
273
+ }
274
+ //# sourceMappingURL=transcribe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcribe.js","sourceRoot":"","sources":["../src/transcribe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /** dsh-asr-voice — host 半区:动态获取供应商 ASR 模型列表。
2
+ * GET /api/asr-voice/asr-models?providerId=X —— 用该供应商的 baseUrl+apiKey
3
+ * 调用 OpenAI-compatible `GET {baseUrl}/models`,过滤出 ASR 相关模型
4
+ * (模型名/ID 含 asr/audio/omni/whisper/sensevoice/voice 等),供设置页
5
+ * 「获取模型」按钮把最新模型填进下拉。key 只在服务端,不进浏览器。
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ import type { IncomingMessage, ServerResponse } from 'node:http';
9
+ import type { KeyRefSource } from './key-ref.ts';
10
+ /** 单个供应商配置面(来自 settings providers 列表)。 */
11
+ export interface CloudProviderLike extends KeyRefSource {
12
+ baseUrl: string;
13
+ apiKey: string;
14
+ model: string;
15
+ mode: string;
16
+ }
17
+ /** 上游模型列表抓取超时(毫秒):上游网络卡死不该拖死整个响应。optimize.ts 的
18
+ * ctx.llm.listModels 竞速用同一常数——「枚举模型」的等待上限全仓一致。 */
19
+ export declare const LIST_MODELS_TIMEOUT_MS = 20000;
20
+ /** 注册 /api/asr-voice/asr-models 路由(GET)。
21
+ * @param register - webserver 的 register 方法。
22
+ * @param getProviders - 读取全部已配置供应商列表的 thunk。
23
+ * @param ctx - host context(供 MiMo key 兜底走 credentials 服务)。
24
+ */
25
+ export declare function registerAsrModelsRoute(register: (def: {
26
+ kind: 'exact';
27
+ path: string;
28
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
29
+ }) => () => void, getProviders: () => CloudProviderLike[], ctx: Context): () => void;
30
+ //# sourceMappingURL=asr-models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asr-models.d.ts","sourceRoot":"","sources":["../../src/asr-models.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,0CAA0C;AAC1C,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb;AASD;kDACkD;AAClD,eAAO,MAAM,sBAAsB,QAAS,CAAA;AAiB5C;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE,KAAK,MAAM,IAAI,EAC5I,YAAY,EAAE,MAAM,iBAAiB,EAAE,EACvC,GAAG,EAAE,OAAO,GACX,MAAM,IAAI,CAyCZ"}
@@ -0,0 +1,41 @@
1
+ /** dsh-asr-voice — host 半区:HTTP 小工具(读 body / 写 JSON / 信任围栏)。 纯 Node 标准库 + 全局 fetch(Node 18+),无平台专属依赖 → macOS / Windows 双平台。 */
2
+ import type { IncomingMessage, ServerResponse } from 'node:http';
3
+ /** body 读取阶段的错误:自带语义化 HTTP 状态码(400 非法 JSON / 408 读取超时 / 413 超限),
4
+ * 由路由 catch 直接映射。早先这些错误只带 message、路由一律回 502,把「客户端发了 30MB
5
+ * 音频」记成「上游故障」——排查时被引向服务商,实际是本地就能判定并立刻纠正的输入问题。
6
+ * 实测:`req.destroy(err)` 抛出的就是这个对象本身(instanceof 存活),超限在 for-await
7
+ * 里直接 throw 也不会毁掉 socket,413 能真正写到客户端。 */
8
+ export declare class HttpBodyError extends Error {
9
+ readonly status: 400 | 408 | 413;
10
+ constructor(status: 400 | 408 | 413, message: string);
11
+ }
12
+ /** 从 catch 到的错误取应回的状态码:{@link HttpBodyError} 用自带 status,其余回退 fallback
13
+ * (路由传 502 = 真正的上游/服务端故障)。 */
14
+ export declare function statusOfBodyError(error: unknown, fallback: number): number;
15
+ /** 读取请求原始 body(Buffer),超限报错;读取停滞超过 timeoutMs 则销毁连接。 */
16
+ export declare function readRawBody(req: IncomingMessage, maxBytes: number, timeoutMs?: number): Promise<Buffer>;
17
+ /** 读取请求 body 并解析为 JSON(超限报错)。 */
18
+ export declare function readJsonBody(req: IncomingMessage, maxBytes?: number): Promise<unknown>;
19
+ /** 写 JSON 响应。 */
20
+ export declare function sendJson(res: ServerResponse, status: number, payload: unknown): void;
21
+ /** 信任围栏:只接受本机回环请求,防止任意网页 CSRF 借宿主代理调用云端
22
+ * (消耗用户的 API key / 额度)。要点(与 dsh-retry-settings 的 index.ts、
23
+ * dsh-email 的 web.ts 围栏同款):
24
+ * - sec-fetch-site === 'cross-site' 一票拒绝(浏览器注入、页面无法伪造);
25
+ * - Host 严格全等判定回环(127. 宽前缀会被 127.0.0.1.evil.com 之类 DNS rebinding 绕过);
26
+ * - 带 Origin 的请求要求同源且 Host 本身是回环——仅凭 originName === hostName 时,
27
+ * 解析到 127.0.0.1 的攻击者域名(rebinding 惯用手法)即构成 Host/Origin 相等的
28
+ * "同源"表象,Host 非回环一律不可信;
29
+ * - Origin/Host 解析失败(如字面量 Origin: null)一律不可信,不抛异常冒泡路由。
30
+ */
31
+ export declare function isTrusted(req: IncomingMessage): boolean;
32
+ /** 路由守卫:信任围栏 + method 白名单。通过返回 null;不通过返回已写好的 403/405 响应值(handler 直接 return 它)。 */
33
+ export declare function guardRoute(req: IncomingMessage, methods?: string[]): {
34
+ ok: false;
35
+ status: 403 | 405;
36
+ payload: {
37
+ ok: false;
38
+ reason: string;
39
+ };
40
+ } | null;
41
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AAAA,+HAA+H;AAC/H,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE;;;;2CAI2C;AAC3C,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;gBACrB,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM;CAKrD;AAED;+BAC+B;AAC/B,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,uDAAuD;AACvD,wBAAsB,WAAW,CAAC,GAAG,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAoB7G;AAED,iCAAiC;AACjC,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,QAAQ,SAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAQhG;AAED,iBAAiB;AACjB,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAQpF;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CA0BvD;AAED,mFAAmF;AACnF,wBAAgB,UAAU,CACxB,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,MAAM,EAAa,GAC3B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,OAAO,EAAE;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,IAAI,CAIjF"}
@@ -0,0 +1,75 @@
1
+ /** dsh-asr-voice — host 半区(组合器)。
2
+ * 职责:
3
+ * - 注册插件配置 namespace `asr-voice`(设置页「语音输入」卡片的权威源)
4
+ * - /api/asr-voice/transcribe —— 云端 ASR 代理(浏览器上传音频,host 转发;支持多供应商)
5
+ * - /api/asr-voice/optimize —— LLM 提示词优化代理
6
+ * - /api/asr-voice/models —— 枚举 DSH 已配置模型(优化模型选择器)
7
+ * - /api/asr-voice/asr-models —— 动态获取某供应商的 ASR 模型(设置页「获取模型」)
8
+ * - /api/asr-voice/stats —— ASR 用量统计(计费相关,低优先级)
9
+ * - 启动时一次性迁移:settings 里的遗留明文 key → DSH credentials,随后抹掉明文
10
+ * LLM 优化默认走 DSH 当前所选 LLM(ctx.agentDefaultModel + ctx.llm),无需
11
+ * 插件单独配 key。云端 ASR 支持多供应商(asr.cloud.providers + active),但
12
+ * settings 里只有 baseUrl / model / mode 等无密钥元数据:API key 存 DSH
13
+ * credentials(引用名见 src/key-ref.ts),浏览器只经私有 JSON 路由调用,拿不到 key。
14
+ * 纯 Node HTTP + 官方 LLM 通道,无平台专属二进制 → macOS / Windows 双平台。
15
+ */
16
+ import type { Context } from '@deepseek-ai/cordis';
17
+ import { type AsrVoiceSettings } from './settings.ts';
18
+ import { type CloudAsrConfig } from './transcribe.ts';
19
+ import { type CloudProviderLike } from './asr-models.ts';
20
+ /** Host context slice this plugin consumes (webServer/llm/settings via type merges). */
21
+ type AsrVoiceHostContext = Context;
22
+ export declare const name = "dsh-asr-voice";
23
+ /** 所需 Cordis 服务(服务名,非 entry id)。 */
24
+ export declare const inject: string[];
25
+ /**
26
+ * 供应商行归一化:settings 深层结构(多供应商/旧单配置)里同一组字段的取值
27
+ * 规则一致(缺省 preset=openai、mode=auto、其余空串),所有读取路径共用
28
+ * 这一个视图,避免四处维护同一份兜底规则。
29
+ * @param row 供应商原始行(name/id 均可缺省:name 用于显示,id 用于引用派生)。
30
+ * @param idFallback 行缺 id 时的兜底 id。
31
+ * @param modeOverride 显式 mode(undefined = 取行内 mode,缺省 'auto')。
32
+ */
33
+ export declare function providerView(row: {
34
+ id?: string;
35
+ preset?: string;
36
+ name?: string;
37
+ baseUrl?: string;
38
+ apiKey?: string;
39
+ model?: string;
40
+ mode?: string;
41
+ }, idFallback: string, modeOverride?: string): {
42
+ id: string;
43
+ preset: string;
44
+ name: string;
45
+ baseUrl: string;
46
+ apiKey: string;
47
+ model: string;
48
+ mode: string;
49
+ };
50
+ /** 从 settings 解析当前生效的云端 ASR 供应商(多供应商 active/首个,或旧单配置)。
51
+ * 内部导出:单测直连。 */
52
+ export declare function resolveCloudProvider(v: AsrVoiceSettings | undefined): CloudAsrConfig | undefined;
53
+ /** 读取全部已配置供应商(多供应商列表;旧单配置合成一个 'legacy')。
54
+ * 内部导出:单测直连。 */
55
+ export declare function listProviders(v: AsrVoiceSettings | undefined): CloudProviderLike[];
56
+ /** DSH credentials 服务的最小面(可选服务,本插件不把它列为硬依赖)。 */
57
+ interface CredentialsLike {
58
+ set(ref: unknown, value: string): Promise<void>;
59
+ }
60
+ /** 一次性迁移:把 settings 里遗留的明文 API key 搬进 DSH credentials,全部搬成功后抹掉明文。
61
+ * 任一条搬不动(凭据服务缺席、该引用被只读来源拒绝)就整批原样留着——抹掉一把无处可寻的
62
+ * key 比留一份本机明文更糟。{@link resolveApiKey} 始终先读 settings,所以未迁移状态下功能
63
+ * 不降级;迁移成功后 settings 里的 key 恒为空,密钥只剩 credentials 一个来源。
64
+ * 内部导出(非插件公共 API):scope/credentials/log 全部可注入,node 单测直连。
65
+ */
66
+ export declare function migrateLegacyKeys(scope: {
67
+ get(): AsrVoiceSettings;
68
+ update(patch: object): Promise<void>;
69
+ }, credentials: CredentialsLike | undefined, log: {
70
+ warn(message: string): void;
71
+ info(message: string): void;
72
+ }): Promise<void>;
73
+ export declare function apply(ctx: AsrVoiceHostContext): void;
74
+ export {};
75
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAOnD,OAAO,EAAwD,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE5G,OAAO,EAA0C,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE9F,OAAO,EAA0B,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAQjF,wFAAwF;AACxF,KAAK,mBAAmB,GAAG,OAAO,CAAC;AAEnC,eAAO,MAAM,IAAI,kBAAkB,CAAC;AAEpC,oCAAoC;AAGpC,eAAO,MAAM,MAAM,UAAuB,CAAC;AAE3C;;;;;;;GAOG;AAGH,wBAAgB,YAAY,CAC1B,GAAG,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EACtH,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAU5G;AAED;gBACgB;AAChB,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,gBAAgB,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAShG;AAED;gBACgB;AAChB,wBAAgB,aAAa,CAAC,CAAC,EAAE,gBAAgB,GAAG,SAAS,GAAG,iBAAiB,EAAE,CAUlF;AAED,gDAAgD;AAChD,UAAU,eAAe;IACvB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChD;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE;IAAE,GAAG,IAAI,gBAAgB,CAAC;IAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,EACxE,WAAW,EAAE,eAAe,GAAG,SAAS,EACxC,GAAG,EAAE;IAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAChE,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAkDD,wBAAgB,KAAK,CAAC,GAAG,EAAE,mBAAmB,GAAG,IAAI,CA0DpD"}
@@ -0,0 +1,9 @@
1
+ /** 一个参与引用派生的供应商身份(settings 行里不含任何密钥材料)。 */
2
+ export interface KeyRefSource {
3
+ preset: string;
4
+ name: string;
5
+ id: string;
6
+ }
7
+ /** 求某供应商的 API key 引用名。 @param p - 供应商身份(预置 id、显示名、行 id)。 @returns credentials 服务里的引用名。 */
8
+ export declare function keyRefFor(p: KeyRefSource): string;
9
+ //# sourceMappingURL=key-ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-ref.d.ts","sourceRoot":"","sources":["../../src/key-ref.ts"],"names":[],"mappings":"AAmBA,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX;AAED,0FAA0F;AAC1F,wBAAgB,SAAS,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAKjD"}
@@ -0,0 +1,41 @@
1
+ /** dsh-asr-voice — host 半区:LLM 提示词优化。
2
+ * 提示词优化只使用 **DSH 已配置好的模型**(ctx.llm 通道):
3
+ * - 请求可指定 { provider, model }(必须是 DSH 模型列表里已配置的)
4
+ * - 未指定 → 用当前所选 LLM(ctx.agentDefaultModel.currentSelection())
5
+ * 要自定义模型,须先到 DSH 原生模型列表添加(本插件不做独立 baseUrl/apiKey)。
6
+ * /api/asr-voice/models 枚举 DSH 已配置模型(供设置页选择器)。
7
+ * API key 由 DSH provider 管理,全程在服务端;跨平台。
8
+ */
9
+ import type { Context } from '@deepseek-ai/cordis';
10
+ import type { IncomingMessage, ServerResponse } from 'node:http';
11
+ /** 一个 DSH 已配置模型的条目(给设置页选择器用)。 */
12
+ export interface DshModelEntry {
13
+ id: string;
14
+ name: string;
15
+ }
16
+ /** 一个 DSH 已配置 provider 及其模型。 */
17
+ export interface DshProviderEntry {
18
+ provider: string;
19
+ name: string;
20
+ models: DshModelEntry[];
21
+ }
22
+ /** 模型选择目标(来自设置页选择的 DSH 模型,或当前所选)。 */
23
+ export interface OptimizeTarget {
24
+ provider: string;
25
+ model: string;
26
+ }
27
+ /** 枚举 DSH 已配置模型(ctx.llm.listProviders + listModels)。 枚举失败/不可用/超时的 provider 给空模型列表(不阻断整体)。 */
28
+ export declare function enumerateModels(ctx: Context): Promise<DshProviderEntry[]>;
29
+ /** 注册 /api/asr-voice/optimize 路由。 请求体:{ text, provider?, model? }——provider/model 须为 DSH 已配置模型; 缺省用当前所选 LLM。 */
30
+ export declare function registerOptimizeRoute(register: (def: {
31
+ kind: 'exact';
32
+ path: string;
33
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
34
+ }) => () => void, ctx: Context): () => void;
35
+ /** 注册 /api/asr-voice/models 路由:枚举 DSH 已配置模型(设置页选择器用)。 */
36
+ export declare function registerModelsRoute(register: (def: {
37
+ kind: 'exact';
38
+ path: string;
39
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
40
+ }) => () => void, ctx: Context): () => void;
41
+ //# sourceMappingURL=optimize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimize.d.ts","sourceRoot":"","sources":["../../src/optimize.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAwBjE,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;CACb;AAED,gCAAgC;AAChC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,aAAa,EAAE,CAAA;CACxB;AAED,qCAAqC;AACrC,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd;AA0BD,6FAA6F;AAC7F,wBAAsB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAa/E;AAoED,gHAAgH;AAChH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE,KAAK,MAAM,IAAI,EAC5I,GAAG,EAAE,OAAO,GACX,MAAM,IAAI,CAwBZ;AAED,yDAAyD;AACzD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE,KAAK,MAAM,IAAI,EAC5I,GAAG,EAAE,OAAO,GACX,MAAM,IAAI,CAgBZ"}
@@ -0,0 +1,64 @@
1
+ /** dsh-asr-voice — 云端 ASR 预置表(host 与 client 共享,双半区各自编译)。
2
+ * 全部走 OpenAI-compatible `/audio/transcriptions` 端点,统一字段:
3
+ * baseUrl + apiKey + model
4
+ * 预置只是「快捷填充」,用户可任意改 baseUrl / model(自定义端点天然兼容
5
+ * 本地/私有部署的 OpenAI-compatible ASR 服务,如 local-ai)。
6
+ * 跨平台说明:云端 ASR 是纯 HTTP,macOS / Windows 行为一致。
7
+ */
8
+ /** 云端 ASR 调用通道:whisper 式 multipart /audio/transcriptions,或 chat-completions input_audio(MiMo / Qwen-ASR)。 */
9
+ export type CloudAsrMode = 'auto' | 'transcriptions' | 'chat';
10
+ /** 一个云端 ASR 预置。 */
11
+ export interface CloudPreset {
12
+ /** 稳定 id(settings 里存的 preset 值)。 */
13
+ id: string;
14
+ /** 设置页显示名。 */
15
+ label: string;
16
+ /** OpenAI-compatible base URL(不含 /audio/transcriptions)。 */
17
+ baseUrl: string;
18
+ /** 默认模型(可改)。 */
19
+ defaultModel: string;
20
+ /** 调用通道(auto = 按模型名自动判定;transcriptions = whisper 式 /audio/transcriptions;chat = chat.completions input_audio)。 */
21
+ mode: CloudAsrMode;
22
+ /** 简介(设置页提示)。 */
23
+ hint: string;
24
+ }
25
+ /** 内置预置:OpenAI / Groq(国际)+ 硅基流动 / 小米 MiMo / 通义 Qwen-ASR(国产)。 */
26
+ export declare const CLOUD_PRESETS: readonly CloudPreset[];
27
+ /** 按 id 取预置(找不到返回 undefined)。 */
28
+ export declare function presetById(id: string): CloudPreset | undefined;
29
+ /** 实时转写 provider 预置(I5:真云端 provider 行)。
30
+ * 与 CLOUD_PRESETS 独立:实时走 WebSocket(wss://…/api-ws/v1/realtime),
31
+ * 不是 OpenAI-compatible HTTP。凭据复用同名官方 LLM provider(keyPreset 指回
32
+ * CLOUD_PRESETS 里的预置 id,`keyRefFor` 因此派生成 `<PRESET>_API_KEY`)。
33
+ */
34
+ export interface RealtimePreset {
35
+ /** 稳定 id(settings `realtime.provider` 存的值;'' = 未配置走内置模拟)。 */
36
+ id: string;
37
+ /** 设置页显示名。 */
38
+ label: string;
39
+ /** WebSocket 根地址(不含 model query,由 defaultModel 拼上)。 */
40
+ wssUrl: string;
41
+ /** 默认实时模型(可改)。 */
42
+ defaultModel: string;
43
+ /** 凭据复用哪个 CLOUD_PRESETS 预置(keyRefFor 派生引用名)。 */
44
+ keyPreset: string;
45
+ /** 简介(设置页提示)。 */
46
+ hint: string;
47
+ }
48
+ /** 内置实时预置:阿里云百炼 Qwen-ASR Realtime(服务端 VAD 断句)。 */
49
+ export declare const REALTIME_PRESETS: readonly RealtimePreset[];
50
+ /** 按 id 取实时预置(找不到返回 undefined)。 */
51
+ export declare function realtimePresetById(id: string): RealtimePreset | undefined;
52
+ /** 预置默认 id。 */
53
+ export declare const DEFAULT_PRESET_ID = "openai";
54
+ /** 云端 ASR 端点(拼在 baseUrl 之后,whisper 式通道用)。 */
55
+ export declare const TRANSCRIBE_PATH = "/audio/transcriptions";
56
+ /** chat-completions 通道端点(拼在 baseUrl 之后)。 */
57
+ export declare const CHAT_COMPLETIONS_PATH = "/chat/completions";
58
+ /** 上传/代理的音频大小上限(字节),防滥用。 */
59
+ export declare const MAX_AUDIO_BYTES: number;
60
+ /** 按模型名自动判定调用通道:chat 音频大模型 vs whisper 式 /audio/transcriptions。 */
61
+ export declare function autoModeForModel(model: string): CloudAsrMode;
62
+ /** 解析最终调用通道:显式模式优先,auto 按模型名判定。 */
63
+ export declare function resolveAsrMode(mode: string, model: string): CloudAsrMode;
64
+ //# sourceMappingURL=presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/presets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,6GAA6G;AAC7G,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,gBAAgB,GAAG,MAAM,CAAA;AAE7D,mBAAmB;AACnB,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,cAAc;IACd,KAAK,EAAE,MAAM,CAAA;IACb,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,kHAAkH;IAClH,IAAI,EAAE,YAAY,CAAA;IAClB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,gEAAgE;AAChE,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAyC/C,CAAA;AAED,iCAAiC;AACjC,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAE9D;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAA;IACV,cAAc;IACd,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAA;IACd,kBAAkB;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,kDAAkD;AAClD,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAiBrD,CAAA;AAED,mCAAmC;AACnC,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEzE;AAED,eAAe;AACf,eAAO,MAAM,iBAAiB,WAAW,CAAA;AAEzC,6CAA6C;AAC7C,eAAO,MAAM,eAAe,0BAA0B,CAAA;AAEtD,4CAA4C;AAC5C,eAAO,MAAM,qBAAqB,sBAAsB,CAAA;AAExD,4BAA4B;AAC5B,eAAO,MAAM,eAAe,QAAmB,CAAA;AAK/C,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAE5D;AAED,mCAAmC;AACnC,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAGxE"}
@@ -0,0 +1,41 @@
1
+ /** dsh-asr-voice — I5:真云端实时 provider(阿里云百炼 Qwen-ASR-Realtime)。
2
+ * 协议(对齐 OpenAI Realtime 兼容面,官方文档:
3
+ * help.aliyun.com/zh/model-studio/qwen-asr-realtime-api):
4
+ * WebSocket URL = `wss://dashscope.aliyuncs.com/api-ws/v1/realtime?model=<model>`
5
+ * 认证 = 握手阶段 `Authorization: Bearer <api_key>`(失败 = HTTP 401/403)
6
+ * 客户端事件 = session.update(pcm / 16000 / server_vad)→ input_audio_buffer.append
7
+ * (base64 PCM)→ … → session.finish(推完必须先发,再关连接)
8
+ * 服务端事件 = session.created / speech_started / speech_stopped /
9
+ * conversation.item.input_audio_transcription.text(partial:
10
+ * text=已确认前缀 + stash=草稿后缀,拼接即完整预览)/
11
+ * …completed(final:transcript)/ …failed / error / session.finished
12
+ * 接缝契约(src/realtime-provider.ts):connect() → RealtimeProviderConnection,
13
+ * send(pcm) 上行 int16 LE / onEvent 事件下行 / close() 幂等。I5 只替换 host 侧
14
+ * `createProvider` 的工厂,RealtimeHost 的会话注册表与 4 条路由一行不改。
15
+ * 依赖:Node 全局 WebSocket(undici,Node 22+ 稳定;本仓 tsconfig host 用 Node 类型),
16
+ * 握手带自定义头已由 test/ws-auth.test.mjs 给出真实 socket 上线证据。
17
+ */
18
+ import type { RealtimeProvider } from './realtime-provider.ts';
19
+ /** 真 provider 的构造参数(host 半区按配置注入)。 */
20
+ export interface DashscopeRealtimeOptions {
21
+ /** DSH 凭据解析出的 API key(Bearer 后接的内容)。 */
22
+ apiKey: string;
23
+ /** 模型名(URL query `model=`,默认 qwen3-asr-flash-realtime)。 */
24
+ model?: string;
25
+ /** WebSocket 根地址(不含 query),默认百炼公共域名。 */
26
+ wssUrl?: string;
27
+ /** 识别语言(可选;省略 = 服务端自动检测)。 */
28
+ language?: string;
29
+ /** 建连兜底超时(毫秒),默认 15s。测试注入小值以确定性覆盖超时路径。 */
30
+ connectTimeoutMs?: number;
31
+ /** 服务端 VAD 参数(可选;默认对齐官方推荐)。 */
32
+ vad?: {
33
+ /** VAD 灵敏度(-1~1,推荐 0.0)。 */
34
+ threshold?: number;
35
+ /** 静音持续多久断句(ms,200~6000,推荐 400)。 */
36
+ silenceDurationMs?: number;
37
+ };
38
+ }
39
+ /** 真云端实时 provider 工厂(I5:host 侧 createProvider 用它)。 */
40
+ export declare function createDashscopeRealtimeProvider(opts: DashscopeRealtimeOptions): RealtimeProvider;
41
+ //# sourceMappingURL=realtime-dashscope.d.ts.map