@hyperdreamer/pi-webui 1.13.1 → 1.14.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 (50) hide show
  1. package/dist/cli.js +19 -4
  2. package/dist/cli.js.map +1 -1
  3. package/dist/client/assets/CodeViewer-BEbclxBf.js +4 -0
  4. package/dist/client/assets/{UnifiedDiffViewer-ag9S0DML.js → UnifiedDiffViewer-DXWhrIST.js} +1 -1
  5. package/dist/client/assets/{index-T2yBZhRq.js → index-2EIg2vwJ.js} +561 -479
  6. package/dist/client/assets/{vendor-editor-core-vUi74DnD.js → vendor-editor-core-vyMPzMNA.js} +1 -1
  7. package/dist/client/assets/vendor-editor-languages-DlEQaRIv.js +46 -0
  8. package/dist/client/index.html +3 -3
  9. package/dist/config.js +288 -4
  10. package/dist/config.js.map +1 -1
  11. package/dist/configMutationCoordinator.js +357 -0
  12. package/dist/configMutationCoordinator.js.map +1 -0
  13. package/dist/server/app.js +64 -1
  14. package/dist/server/app.js.map +1 -1
  15. package/dist/server/configRoutes.js +84 -18
  16. package/dist/server/configRoutes.js.map +1 -1
  17. package/dist/server/index.js +15 -3
  18. package/dist/server/index.js.map +1 -1
  19. package/dist/server/machines/machineProxyRoutes.js +6 -9
  20. package/dist/server/machines/machineProxyRoutes.js.map +1 -1
  21. package/dist/server/sessiond/configMutationWriters.js +18 -0
  22. package/dist/server/sessiond/configMutationWriters.js.map +1 -0
  23. package/dist/server/sessiond.js +7 -7
  24. package/dist/server/sessiond.js.map +1 -1
  25. package/dist/server/sessions/modelTierSettingsRoutes.js +5 -1
  26. package/dist/server/sessions/modelTierSettingsRoutes.js.map +1 -1
  27. package/dist/server/sessions/utilityModelSettingsRoutes.js +5 -1
  28. package/dist/server/sessions/utilityModelSettingsRoutes.js.map +1 -1
  29. package/dist/server/speechInput/openAiCompatibleTranscriptionProvider.js +357 -0
  30. package/dist/server/speechInput/openAiCompatibleTranscriptionProvider.js.map +1 -0
  31. package/dist/server/speechInput/piCompatibleCredentialResolver.js +348 -0
  32. package/dist/server/speechInput/piCompatibleCredentialResolver.js.map +1 -0
  33. package/dist/server/speechInput/speechInputSettingsRoutes.js +39 -0
  34. package/dist/server/speechInput/speechInputSettingsRoutes.js.map +1 -0
  35. package/dist/server/speechInput/speechInputSettingsService.js +247 -0
  36. package/dist/server/speechInput/speechInputSettingsService.js.map +1 -0
  37. package/dist/server/speechInput/speechInputTranscriptionRoutes.js +226 -0
  38. package/dist/server/speechInput/speechInputTranscriptionRoutes.js.map +1 -0
  39. package/dist/server/speechInput/speechTranscriptionService.js +116 -0
  40. package/dist/server/speechInput/speechTranscriptionService.js.map +1 -0
  41. package/dist/shared/apiTypes.d.ts +54 -0
  42. package/dist/shared/apiTypes.js.map +1 -1
  43. package/dist/shared/speechInput.js +75 -0
  44. package/dist/shared/speechInput.js.map +1 -0
  45. package/dist/shared/speechInputAudio.js +42 -0
  46. package/dist/shared/speechInputAudio.js.map +1 -0
  47. package/docs/config.md +86 -1
  48. package/package.json +1 -1
  49. package/dist/client/assets/CodeViewer-BPgv1edR.js +0 -4
  50. package/dist/client/assets/vendor-editor-languages-DoaXUodB.js +0 -46
@@ -0,0 +1,348 @@
1
+ import { spawn } from "node:child_process";
2
+ export const SPEECH_INPUT_CREDENTIAL_COMMAND_TIMEOUT_MS = 10_000;
3
+ export const SPEECH_INPUT_CREDENTIAL_MAX_STDOUT_BYTES = 64 * 1024;
4
+ const credentialSourceUnresolvedMessage = "Credential source could not be resolved";
5
+ const credentialCommandFailedMessage = "Credential command failed";
6
+ const credentialCommandCancelledMessage = "Credential command was cancelled";
7
+ const credentialCommandTimedOutMessage = "Credential command timed out";
8
+ const credentialCommandOutputExceededMessage = "Credential command output exceeded limit";
9
+ const credentialCommandEmptyOutputMessage = "Credential command produced no credential";
10
+ const variableNamePattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
11
+ const variableNameContinuationPattern = /^[A-Za-z0-9_]$/;
12
+ class CredentialResolutionError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "CredentialResolutionError";
16
+ }
17
+ }
18
+ export function inspectPiCompatibleCredentialSource(source, env) {
19
+ if (source === undefined || source === "")
20
+ return { configured: false, resolution: "missing" };
21
+ const parsed = parseCredentialSource(source);
22
+ if (parsed.kind === "command")
23
+ return { configured: true, source: "command", resolution: "unchecked" };
24
+ if (parsed.kind === "literal")
25
+ return { configured: true, source: "literal", resolution: "resolved" };
26
+ const resolved = resolveTemplate(parsed.template, env);
27
+ return {
28
+ configured: true,
29
+ source: "environment",
30
+ resolution: resolved === undefined ? "unresolved" : "resolved",
31
+ };
32
+ }
33
+ export async function resolvePiCompatibleCredentialSource(source, options) {
34
+ if (source === undefined || source === "")
35
+ throw new CredentialResolutionError(credentialSourceUnresolvedMessage);
36
+ const parsed = parseCredentialSource(source);
37
+ if (parsed.kind === "command") {
38
+ if (options.signal.aborted)
39
+ throw new CredentialResolutionError(credentialCommandCancelledMessage);
40
+ try {
41
+ const value = await (options.runCommand ?? createCredentialCommandRunner())({
42
+ command: parsed.command,
43
+ signal: options.signal,
44
+ timeoutMs: SPEECH_INPUT_CREDENTIAL_COMMAND_TIMEOUT_MS,
45
+ maxStdoutBytes: SPEECH_INPUT_CREDENTIAL_MAX_STDOUT_BYTES,
46
+ });
47
+ if (value.trim() === "")
48
+ throw new CredentialResolutionError(credentialCommandEmptyOutputMessage);
49
+ return value;
50
+ }
51
+ catch (error) {
52
+ if (error instanceof CredentialResolutionError)
53
+ throw error;
54
+ throw new CredentialResolutionError(credentialCommandFailedMessage);
55
+ }
56
+ }
57
+ const value = resolveTemplate(parsed.template, options.env);
58
+ if (value === undefined)
59
+ throw new CredentialResolutionError(credentialSourceUnresolvedMessage);
60
+ return value;
61
+ }
62
+ export function createCredentialCommandRunner(host = createDefaultCredentialProcessHost()) {
63
+ return async (request) => {
64
+ if (request.signal.aborted)
65
+ throw new CredentialResolutionError(credentialCommandCancelledMessage);
66
+ return await new Promise((resolve, reject) => {
67
+ let process;
68
+ try {
69
+ process = host.spawn(request.command);
70
+ }
71
+ catch {
72
+ reject(new CredentialResolutionError(credentialCommandFailedMessage));
73
+ return;
74
+ }
75
+ let settled = false;
76
+ let stdoutBytes = 0;
77
+ const stdoutChunks = [];
78
+ let removeStdout = () => undefined;
79
+ let removeStderr = () => undefined;
80
+ let removeClose = () => undefined;
81
+ let cancelDeadline = () => undefined;
82
+ const lifecycle = { cleanupRequested: false };
83
+ const cleanup = () => {
84
+ lifecycle.cleanupRequested = true;
85
+ runCleanup(removeStdout);
86
+ runCleanup(removeStderr);
87
+ runCleanup(removeClose);
88
+ runCleanup(() => { request.signal.removeEventListener("abort", onAbort); });
89
+ runCleanup(cancelDeadline);
90
+ };
91
+ const terminate = () => {
92
+ if (process.pid === undefined)
93
+ return;
94
+ try {
95
+ host.terminateTrackedProcesses(process);
96
+ }
97
+ catch {
98
+ // Resolution ownership has settled; cleanup is best effort.
99
+ }
100
+ };
101
+ const finish = (outcome) => {
102
+ if (settled)
103
+ return;
104
+ settled = true;
105
+ cleanup();
106
+ if ("value" in outcome) {
107
+ resolve(outcome.value);
108
+ return;
109
+ }
110
+ if (outcome.terminate)
111
+ terminate();
112
+ reject(outcome.error);
113
+ };
114
+ const onAbort = () => {
115
+ finish({ error: new CredentialResolutionError(credentialCommandCancelledMessage), terminate: true });
116
+ };
117
+ const onStdout = (chunk) => {
118
+ if (settled)
119
+ return;
120
+ stdoutBytes += chunk.byteLength;
121
+ if (stdoutBytes > request.maxStdoutBytes) {
122
+ finish({ error: new CredentialResolutionError(credentialCommandOutputExceededMessage), terminate: true });
123
+ return;
124
+ }
125
+ stdoutChunks.push(chunk);
126
+ };
127
+ const onClose = (result) => {
128
+ if (result.error !== undefined || result.code !== 0) {
129
+ finish({ error: new CredentialResolutionError(credentialCommandFailedMessage), terminate: false });
130
+ return;
131
+ }
132
+ const output = Buffer.concat(stdoutChunks).toString("utf8").trim();
133
+ if (output === "") {
134
+ finish({ error: new CredentialResolutionError(credentialCommandEmptyOutputMessage), terminate: false });
135
+ return;
136
+ }
137
+ finish({ value: output });
138
+ };
139
+ // Streams and close must be observed before cancellation is exposed, so an
140
+ // immediately aborted caller cannot leave a spawned process unobserved.
141
+ try {
142
+ cancelDeadline = host.scheduleDeadline(() => {
143
+ finish({ error: new CredentialResolutionError(credentialCommandTimedOutMessage), terminate: true });
144
+ }, request.timeoutMs);
145
+ }
146
+ catch {
147
+ finish({ error: new CredentialResolutionError(credentialCommandFailedMessage), terminate: true });
148
+ return;
149
+ }
150
+ if (removeAfterSynchronousSettlement(lifecycle, cancelDeadline))
151
+ return;
152
+ try {
153
+ removeStdout = process.onStdout(onStdout);
154
+ if (removeAfterSynchronousSettlement(lifecycle, removeStdout))
155
+ return;
156
+ removeStderr = process.onStderr(() => undefined);
157
+ removeClose = process.onClose(onClose);
158
+ if (removeAfterSynchronousSettlement(lifecycle, removeClose))
159
+ return;
160
+ }
161
+ catch {
162
+ finish({ error: new CredentialResolutionError(credentialCommandFailedMessage), terminate: true });
163
+ return;
164
+ }
165
+ request.signal.addEventListener("abort", onAbort, { once: true });
166
+ if (request.signal.aborted)
167
+ onAbort();
168
+ });
169
+ };
170
+ }
171
+ function removeAfterSynchronousSettlement(lifecycle, remove) {
172
+ if (!lifecycle.cleanupRequested)
173
+ return false;
174
+ runCleanup(remove);
175
+ return true;
176
+ }
177
+ function runCleanup(cleanup) {
178
+ try {
179
+ cleanup();
180
+ }
181
+ catch {
182
+ // Credential resolution has already chosen its terminal outcome.
183
+ }
184
+ }
185
+ function parseCredentialSource(source) {
186
+ if (source.startsWith("!"))
187
+ return { kind: "command", command: source.slice(1) };
188
+ const template = parseCredentialTemplate(source);
189
+ return template.hasVariable ? { kind: "environment", template } : { kind: "literal", template };
190
+ }
191
+ function parseCredentialTemplate(source) {
192
+ const parts = [];
193
+ let literal = "";
194
+ let hasVariable = false;
195
+ const appendLiteral = (value) => { literal += value; };
196
+ const flushLiteral = () => {
197
+ if (literal === "")
198
+ return;
199
+ parts.push({ type: "literal", value: literal });
200
+ literal = "";
201
+ };
202
+ for (let index = 0; index < source.length; index += 1) {
203
+ const character = source[index];
204
+ if (character === undefined)
205
+ continue;
206
+ if (character !== "$") {
207
+ appendLiteral(character);
208
+ continue;
209
+ }
210
+ const next = source[index + 1];
211
+ if (next === "$") {
212
+ appendLiteral("$");
213
+ index += 1;
214
+ continue;
215
+ }
216
+ if (next === "!") {
217
+ appendLiteral("!");
218
+ index += 1;
219
+ continue;
220
+ }
221
+ if (next === "{") {
222
+ const closingIndex = source.indexOf("}", index + 2);
223
+ if (closingIndex === -1) {
224
+ appendLiteral("$");
225
+ continue;
226
+ }
227
+ const name = source.slice(index + 2, closingIndex);
228
+ if (!variableNamePattern.test(name)) {
229
+ appendLiteral(source.slice(index, closingIndex + 1));
230
+ index = closingIndex;
231
+ continue;
232
+ }
233
+ flushLiteral();
234
+ parts.push({ type: "variable", name });
235
+ hasVariable = true;
236
+ index = closingIndex;
237
+ continue;
238
+ }
239
+ if (next !== undefined && variableNamePattern.test(next)) {
240
+ let endIndex = index + 2;
241
+ while (endIndex < source.length && variableNameContinuationPattern.test(source[endIndex] ?? ""))
242
+ endIndex += 1;
243
+ flushLiteral();
244
+ parts.push({ type: "variable", name: source.slice(index + 1, endIndex) });
245
+ hasVariable = true;
246
+ index = endIndex - 1;
247
+ continue;
248
+ }
249
+ appendLiteral("$");
250
+ }
251
+ flushLiteral();
252
+ return { parts, hasVariable };
253
+ }
254
+ function resolveTemplate(template, env) {
255
+ if (template === undefined)
256
+ return undefined;
257
+ let result = "";
258
+ for (const part of template.parts) {
259
+ if (part.type === "literal") {
260
+ result += part.value;
261
+ continue;
262
+ }
263
+ const suppliedValue = env?.[part.name];
264
+ const processValue = process.env[part.name];
265
+ const value = suppliedValue === undefined || suppliedValue === "" ? processValue : suppliedValue;
266
+ if (value === undefined || value === "")
267
+ return undefined;
268
+ result += value;
269
+ }
270
+ return result;
271
+ }
272
+ function createDefaultCredentialProcessHost() {
273
+ return {
274
+ spawn(command) {
275
+ const child = spawn(command, {
276
+ shell: true,
277
+ detached: process.platform !== "win32",
278
+ stdio: ["ignore", "pipe", "pipe"],
279
+ windowsHide: true,
280
+ });
281
+ return adaptChildProcess(child);
282
+ },
283
+ terminateTrackedProcesses(spawnedProcess) {
284
+ if (spawnedProcess.pid === undefined)
285
+ return;
286
+ if (process.platform !== "win32") {
287
+ try {
288
+ processKillGroup(spawnedProcess.pid);
289
+ }
290
+ catch { /* Best effort cleanup. */ }
291
+ return;
292
+ }
293
+ try {
294
+ const taskkill = spawn("taskkill", ["/PID", String(spawnedProcess.pid), "/T", "/F"], {
295
+ detached: false,
296
+ stdio: "ignore",
297
+ windowsHide: true,
298
+ });
299
+ taskkill.on("error", () => undefined);
300
+ }
301
+ catch {
302
+ // Best effort cleanup.
303
+ }
304
+ },
305
+ scheduleDeadline(callback, delayMs) {
306
+ const timer = setTimeout(callback, delayMs);
307
+ return () => { clearTimeout(timer); };
308
+ },
309
+ };
310
+ }
311
+ function processKillGroup(pid) {
312
+ process.kill(-pid, "SIGKILL");
313
+ }
314
+ function adaptChildProcess(child) {
315
+ const stdout = child.stdout;
316
+ const stderr = child.stderr;
317
+ if (stdout === null || stderr === null)
318
+ throw new Error("Credential process streams unavailable");
319
+ let closeResult;
320
+ const closeListeners = new Set();
321
+ const notifyClose = (result) => {
322
+ if (closeResult !== undefined)
323
+ return;
324
+ closeResult = result;
325
+ for (const listener of closeListeners)
326
+ listener(result);
327
+ };
328
+ child.on("error", (error) => { notifyClose({ code: null, error }); });
329
+ child.on("close", (code) => { notifyClose({ code }); });
330
+ return {
331
+ pid: child.pid,
332
+ onStdout(listener) {
333
+ stdout.on("data", listener);
334
+ return () => { stdout.off("data", listener); };
335
+ },
336
+ onStderr(listener) {
337
+ stderr.on("data", listener);
338
+ return () => { stderr.off("data", listener); };
339
+ },
340
+ onClose(listener) {
341
+ closeListeners.add(listener);
342
+ if (closeResult !== undefined)
343
+ listener(closeResult);
344
+ return () => { closeListeners.delete(listener); };
345
+ },
346
+ };
347
+ }
348
+ //# sourceMappingURL=piCompatibleCredentialResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"piCompatibleCredentialResolver.js","sourceRoot":"","sources":["../../../src/server/speechInput/piCompatibleCredentialResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAI3C,MAAM,CAAC,MAAM,0CAA0C,GAAG,MAAM,CAAC;AACjE,MAAM,CAAC,MAAM,wCAAwC,GAAG,EAAE,GAAG,IAAI,CAAC;AAmClE,MAAM,iCAAiC,GAAG,yCAAyC,CAAC;AACpF,MAAM,8BAA8B,GAAG,2BAA2B,CAAC;AACnE,MAAM,iCAAiC,GAAG,kCAAkC,CAAC;AAC7E,MAAM,gCAAgC,GAAG,8BAA8B,CAAC;AACxE,MAAM,sCAAsC,GAAG,0CAA0C,CAAC;AAC1F,MAAM,mCAAmC,GAAG,2CAA2C,CAAC;AACxF,MAAM,mBAAmB,GAAG,0BAA0B,CAAC;AACvD,MAAM,+BAA+B,GAAG,gBAAgB,CAAC;AAEzD,MAAM,yBAA0B,SAAQ,KAAK;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAiBD,MAAM,UAAU,mCAAmC,CACjD,MAA0B,EAC1B,GAAuB;IAEvB,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;IAE/F,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;IACvG,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;IAEtG,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACvD,OAAO;QACL,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,aAAa;QACrB,UAAU,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU;KAC/D,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mCAAmC,CACvD,MAA0B,EAC1B,OAAiC;IAEjC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE;QAAE,MAAM,IAAI,yBAAyB,CAAC,iCAAiC,CAAC,CAAC;IAElH,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,yBAAyB,CAAC,iCAAiC,CAAC,CAAC;QACnG,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,IAAI,6BAA6B,EAAE,CAAC,CAAC;gBAC1E,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,SAAS,EAAE,0CAA0C;gBACrD,cAAc,EAAE,wCAAwC;aACzD,CAAC,CAAC;YACH,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;gBAAE,MAAM,IAAI,yBAAyB,CAAC,mCAAmC,CAAC,CAAC;YAClG,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,KAAK,YAAY,yBAAyB;gBAAE,MAAM,KAAK,CAAC;YAC5D,MAAM,IAAI,yBAAyB,CAAC,8BAA8B,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5D,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,yBAAyB,CAAC,iCAAiC,CAAC,CAAC;IAChG,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,OAA8B,kCAAkC,EAAE;IAC9G,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,yBAAyB,CAAC,iCAAiC,CAAC,CAAC;QACnG,OAAO,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrD,IAAI,OAAiC,CAAC;YACtC,IAAI,CAAC;gBACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,CAAC,IAAI,yBAAyB,CAAC,8BAA8B,CAAC,CAAC,CAAC;gBACtE,OAAO;YACT,CAAC;YAED,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,WAAW,GAAG,CAAC,CAAC;YACpB,MAAM,YAAY,GAAiB,EAAE,CAAC;YACtC,IAAI,YAAY,GAAe,GAAG,EAAE,CAAC,SAAS,CAAC;YAC/C,IAAI,YAAY,GAAe,GAAG,EAAE,CAAC,SAAS,CAAC;YAC/C,IAAI,WAAW,GAAe,GAAG,EAAE,CAAC,SAAS,CAAC;YAC9C,IAAI,cAAc,GAAe,GAAG,EAAE,CAAC,SAAS,CAAC;YACjD,MAAM,SAAS,GAA+B,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC;YAE1E,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC;gBAClC,UAAU,CAAC,YAAY,CAAC,CAAC;gBACzB,UAAU,CAAC,YAAY,CAAC,CAAC;gBACzB,UAAU,CAAC,WAAW,CAAC,CAAC;gBACxB,UAAU,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC5E,UAAU,CAAC,cAAc,CAAC,CAAC;YAC7B,CAAC,CAAC;YAEF,MAAM,SAAS,GAAG,GAAG,EAAE;gBACrB,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS;oBAAE,OAAO;gBACtC,IAAI,CAAC;oBACH,IAAI,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC;gBAC1C,CAAC;gBAAC,MAAM,CAAC;oBACP,4DAA4D;gBAC9D,CAAC;YACH,CAAC,CAAC;YAEF,MAAM,MAAM,GAAG,CAAC,OAAqF,EAAE,EAAE;gBACvG,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO,EAAE,CAAC;gBACV,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;oBACvB,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;oBACvB,OAAO;gBACT,CAAC;gBACD,IAAI,OAAO,CAAC,SAAS;oBAAE,SAAS,EAAE,CAAC;gBACnC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC,CAAC;YAEF,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,yBAAyB,CAAC,iCAAiC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACvG,CAAC,CAAC;YAEF,MAAM,QAAQ,GAAG,CAAC,KAAiB,EAAE,EAAE;gBACrC,IAAI,OAAO;oBAAE,OAAO;gBACpB,WAAW,IAAI,KAAK,CAAC,UAAU,CAAC;gBAChC,IAAI,WAAW,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;oBACzC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,yBAAyB,CAAC,sCAAsC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;oBAC1G,OAAO;gBACT,CAAC;gBACD,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC,CAAC;YAEF,MAAM,OAAO,GAAG,CAAC,MAA6B,EAAE,EAAE;gBAChD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;oBACpD,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,yBAAyB,CAAC,8BAA8B,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;oBACnG,OAAO;gBACT,CAAC;gBACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBACnE,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;oBAClB,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,yBAAyB,CAAC,mCAAmC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;oBACxG,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;YAC5B,CAAC,CAAC;YAEF,2EAA2E;YAC3E,wEAAwE;YACxE,IAAI,CAAC;gBACH,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE;oBAC1C,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,yBAAyB,CAAC,gCAAgC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACtG,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YACxB,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,yBAAyB,CAAC,8BAA8B,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAClG,OAAO;YACT,CAAC;YACD,IAAI,gCAAgC,CAAC,SAAS,EAAE,cAAc,CAAC;gBAAE,OAAO;YACxE,IAAI,CAAC;gBACH,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAC1C,IAAI,gCAAgC,CAAC,SAAS,EAAE,YAAY,CAAC;oBAAE,OAAO;gBACtE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;gBACjD,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBACvC,IAAI,gCAAgC,CAAC,SAAS,EAAE,WAAW,CAAC;oBAAE,OAAO;YACvE,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,yBAAyB,CAAC,8BAA8B,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAClG,OAAO;YACT,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAClE,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO,EAAE,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,gCAAgC,CAAC,SAAqC,EAAE,MAAkB;IACjG,IAAI,CAAC,SAAS,CAAC,gBAAgB;QAAE,OAAO,KAAK,CAAC;IAC9C,UAAU,CAAC,MAAM,CAAC,CAAC;IACnB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CAAC,OAAmB;IACrC,IAAI,CAAC;QACH,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,iEAAiE;IACnE,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,MAAc;IAC3C,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACjF,MAAM,QAAQ,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IACjD,OAAO,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AAClG,CAAC;AAED,SAAS,uBAAuB,CAAC,MAAc;IAC7C,MAAM,KAAK,GAA6B,EAAE,CAAC;IAC3C,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,WAAW,GAAG,KAAK,CAAC;IAExB,MAAM,aAAa,GAAG,CAAC,KAAa,EAAE,EAAE,GAAG,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,OAAO,KAAK,EAAE;YAAE,OAAO;QAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QAChD,OAAO,GAAG,EAAE,CAAC;IACf,CAAC,CAAC;IAEF,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,SAAS,KAAK,SAAS;YAAE,SAAS;QACtC,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,aAAa,CAAC,SAAS,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAC/B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,aAAa,CAAC,GAAG,CAAC,CAAC;YACnB,KAAK,IAAI,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,aAAa,CAAC,GAAG,CAAC,CAAC;YACnB,KAAK,IAAI,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YACpD,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;gBACxB,aAAa,CAAC,GAAG,CAAC,CAAC;gBACnB,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,YAAY,CAAC,CAAC;YACnD,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC;gBACrD,KAAK,GAAG,YAAY,CAAC;gBACrB,SAAS;YACX,CAAC;YACD,YAAY,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,WAAW,GAAG,IAAI,CAAC;YACnB,KAAK,GAAG,YAAY,CAAC;YACrB,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,SAAS,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACzD,IAAI,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC;YACzB,OAAO,QAAQ,GAAG,MAAM,CAAC,MAAM,IAAI,+BAA+B,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAAE,QAAQ,IAAI,CAAC,CAAC;YAC/G,YAAY,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC1E,WAAW,GAAG,IAAI,CAAC;YACnB,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC;YACrB,SAAS;QACX,CAAC;QACD,aAAa,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAED,YAAY,EAAE,CAAC;IACf,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AAChC,CAAC;AAED,SAAS,eAAe,CAAC,QAAwC,EAAE,GAAkC;IACnG,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC7C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC;YACrB,SAAS;QACX,CAAC;QACD,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,aAAa,KAAK,SAAS,IAAI,aAAa,KAAK,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC;QACjG,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC;IAClB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,kCAAkC;IACzC,OAAO;QACL,KAAK,CAAC,OAAO;YACX,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE;gBAC3B,KAAK,EAAE,IAAI;gBACX,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;gBACtC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;gBACjC,WAAW,EAAE,IAAI;aAClB,CAAC,CAAC;YACH,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;QACD,yBAAyB,CAAC,cAAc;YACtC,IAAI,cAAc,CAAC,GAAG,KAAK,SAAS;gBAAE,OAAO;YAC7C,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBACjC,IAAI,CAAC;oBAAC,gBAAgB,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,0BAA0B,CAAC,CAAC;gBAClF,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;oBACnF,QAAQ,EAAE,KAAK;oBACf,KAAK,EAAE,QAAQ;oBACf,WAAW,EAAE,IAAI;iBAClB,CAAC,CAAC;gBACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,uBAAuB;YACzB,CAAC;QACH,CAAC;QACD,gBAAgB,CAAC,QAAQ,EAAE,OAAO;YAChC,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC5C,OAAO,GAAG,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAmB;IAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC5B,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAClG,IAAI,WAA8C,CAAC;IACnD,MAAM,cAAc,GAAG,IAAI,GAAG,EAA2C,CAAC;IAC1E,MAAM,WAAW,GAAG,CAAC,MAA6B,EAAE,EAAE;QACpD,IAAI,WAAW,KAAK,SAAS;YAAE,OAAO;QACtC,WAAW,GAAG,MAAM,CAAC;QACrB,KAAK,MAAM,QAAQ,IAAI,cAAc;YAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1D,CAAC,CAAC;IAEF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7E,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvE,OAAO;QACL,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,QAAQ,CAAC,QAAQ;YACf,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YAC5B,OAAO,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,CAAC;QACD,QAAQ,CAAC,QAAQ;YACf,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YAC5B,OAAO,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,CAAC,QAAQ;YACd,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7B,IAAI,WAAW,KAAK,SAAS;gBAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;YACrD,OAAO,GAAG,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACpD,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { PiWebUiConfigMutationBusyError } from "../../configMutationCoordinator.js";
2
+ import { SpeechInputSettingsConflictError, SpeechInputSettingsValidationError, } from "./speechInputSettingsService.js";
3
+ const CONFLICT_MESSAGE = "Speech input settings changed. Reload and try again.";
4
+ const BUSY_MESSAGE = "PI WEBUI config is busy. Try again.";
5
+ const UNEXPECTED_MESSAGE = "Speech input settings request failed.";
6
+ /** Gateway-only redacted speech input settings surface; no machine alias exists. */
7
+ export function registerSpeechInputSettingsRoutes(app, service) {
8
+ app.get("/api/speech-input/settings", async (_request, reply) => {
9
+ try {
10
+ return await service.read();
11
+ }
12
+ catch (error) {
13
+ return mapSpeechInputSettingsError(reply, error);
14
+ }
15
+ });
16
+ app.put("/api/speech-input/settings", async (request, reply) => {
17
+ try {
18
+ return await service.update(request.body);
19
+ }
20
+ catch (error) {
21
+ return mapSpeechInputSettingsError(reply, error);
22
+ }
23
+ });
24
+ }
25
+ function mapSpeechInputSettingsError(reply, error) {
26
+ if (error instanceof SpeechInputSettingsValidationError) {
27
+ return reply.code(400).send({ error: error.message });
28
+ }
29
+ if (error instanceof SpeechInputSettingsConflictError) {
30
+ // Stable safe text; never echoes the current revision or settings.
31
+ return reply.code(409).send({ error: CONFLICT_MESSAGE });
32
+ }
33
+ if (error instanceof PiWebUiConfigMutationBusyError) {
34
+ return reply.code(503).send({ error: BUSY_MESSAGE });
35
+ }
36
+ // Never forward unexpected error text: it could carry credential material.
37
+ return reply.code(500).send({ error: UNEXPECTED_MESSAGE });
38
+ }
39
+ //# sourceMappingURL=speechInputSettingsRoutes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speechInputSettingsRoutes.js","sourceRoot":"","sources":["../../../src/server/speechInput/speechInputSettingsRoutes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EACL,gCAAgC,EAChC,kCAAkC,GAEnC,MAAM,iCAAiC,CAAC;AAEzC,MAAM,gBAAgB,GAAG,sDAAsD,CAAC;AAChF,MAAM,YAAY,GAAG,qCAAqC,CAAC;AAC3D,MAAM,kBAAkB,GAAG,uCAAuC,CAAC;AAEnE,oFAAoF;AACpF,MAAM,UAAU,iCAAiC,CAAC,GAAoB,EAAE,OAAmC;IACzG,GAAG,CAAC,GAAG,CAAC,4BAA4B,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;QAC9D,IAAI,CAAC;YACH,OAAO,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,2BAA2B,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAoB,4BAA4B,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAChF,IAAI,CAAC;YACH,OAAO,MAAM,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,2BAA2B,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,2BAA2B,CAAC,KAAmB,EAAE,KAAc;IACtE,IAAI,KAAK,YAAY,kCAAkC,EAAE,CAAC;QACxD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,KAAK,YAAY,gCAAgC,EAAE,CAAC;QACtD,mEAAmE;QACnE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,KAAK,YAAY,8BAA8B,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,2EAA2E;IAC3E,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,247 @@
1
+ import { canonicalBcp47LanguageTag, effectiveSpeechInputSettings, isCanonicalLowercaseUuid, speechInputTranscriptionEndpoint } from "../../shared/speechInput.js";
2
+ import { inspectPiCompatibleCredentialSource } from "./piCompatibleCredentialResolver.js";
3
+ const PRESERVED_CREDENTIAL_ENDPOINT_MESSAGE = "Re-enter the API key source when changing the cloud base URL.";
4
+ const CONFLICT_MESSAGE = "Speech input settings changed. Reload and try again.";
5
+ const MAX_LANGUAGE_LENGTH = 128;
6
+ const MAX_BASE_URL_LENGTH = 2_048;
7
+ const MAX_MODEL_LENGTH = 256;
8
+ const MAX_CREDENTIAL_SOURCE_BYTES = 8 * 1024;
9
+ /** Typed settings validation failure; routes map it to a safe 400. */
10
+ export class SpeechInputSettingsValidationError extends Error {
11
+ constructor() {
12
+ super(...arguments);
13
+ this.code = "SPEECH_INPUT_SETTINGS_VALIDATION";
14
+ }
15
+ }
16
+ /** Typed revision mismatch; routes map it to a safe 409 and never write. */
17
+ export class SpeechInputSettingsConflictError extends Error {
18
+ constructor() {
19
+ super(CONFLICT_MESSAGE);
20
+ this.code = "SPEECH_INPUT_SETTINGS_CONFLICT";
21
+ }
22
+ }
23
+ /**
24
+ * The canonical browser surface for speech input settings. It owns the raw
25
+ * speechInput config knowledge, projects redacted nonsecret settings plus a
26
+ * nonexecuting credential status, and mutates exclusively through the shared
27
+ * cross-process config mutation coordinator with revision CAS semantics.
28
+ */
29
+ export function createSpeechInputSettingsService(dependencies) {
30
+ const { coordinator, env, onCommitted } = dependencies;
31
+ const responseFromSnapshot = (snapshot) => {
32
+ const speech = snapshot.loaded.config.speechInput;
33
+ return {
34
+ contractVersion: 1,
35
+ revision: snapshot.speechInputRevision,
36
+ settings: effectiveSpeechInputSettings(speech),
37
+ credential: inspectPiCompatibleCredentialSource(speech?.cloud?.apiKey, env),
38
+ };
39
+ };
40
+ return {
41
+ async read() {
42
+ return responseFromSnapshot(await coordinator.read());
43
+ },
44
+ async update(value) {
45
+ const update = parseSpeechInputSettingsUpdate(value);
46
+ const committed = await coordinator.mutate((current) => {
47
+ // The revision comparison runs inside the transaction, before any
48
+ // replacement is constructed, so a stale revision never rotates.
49
+ if (update.expectedRevision !== current.speechInputRevision) {
50
+ throw new SpeechInputSettingsConflictError();
51
+ }
52
+ return applyCredentialMutation(current, update);
53
+ }, { rotateSpeechInputRevision: true });
54
+ onCommitted?.();
55
+ return responseFromSnapshot(committed);
56
+ },
57
+ };
58
+ }
59
+ function applyCredentialMutation(current, update) {
60
+ const currentConfig = current.loaded.config;
61
+ const mutation = update.credential;
62
+ if (mutation.action === "preserve")
63
+ return applyPreserve(currentConfig, update);
64
+ if (mutation.action === "replace")
65
+ return applyReplace(currentConfig, update, mutation.value);
66
+ return applyClear(currentConfig);
67
+ }
68
+ function applyPreserve(currentConfig, update) {
69
+ const existingSource = currentConfig.speechInput?.cloud?.apiKey;
70
+ if (existingSource !== undefined && existingSource !== "") {
71
+ const currentEndpoint = speechInputTranscriptionEndpoint(effectiveSpeechInputSettings(currentConfig.speechInput).cloud.baseUrl);
72
+ const submittedEndpoint = speechInputTranscriptionEndpoint(update.settings.cloud.baseUrl);
73
+ if (currentEndpoint !== submittedEndpoint) {
74
+ // A preserved credential must never be redirected to a different cloud
75
+ // endpoint; changing the destination requires replace or a prior clear.
76
+ throw new SpeechInputSettingsValidationError(PRESERVED_CREDENTIAL_ENDPOINT_MESSAGE);
77
+ }
78
+ }
79
+ return {
80
+ ...currentConfig,
81
+ speechInput: {
82
+ provider: update.settings.provider,
83
+ ...(update.settings.language === undefined ? {} : { language: update.settings.language }),
84
+ cloud: {
85
+ baseUrl: update.settings.cloud.baseUrl,
86
+ model: update.settings.cloud.model,
87
+ ...(existingSource === undefined || existingSource === "" ? {} : { apiKey: existingSource }),
88
+ },
89
+ },
90
+ };
91
+ }
92
+ function applyReplace(currentConfig, update, credentialValue) {
93
+ return {
94
+ ...currentConfig,
95
+ speechInput: {
96
+ provider: update.settings.provider,
97
+ ...(update.settings.language === undefined ? {} : { language: update.settings.language }),
98
+ cloud: {
99
+ baseUrl: update.settings.cloud.baseUrl,
100
+ model: update.settings.cloud.model,
101
+ apiKey: credentialValue,
102
+ },
103
+ },
104
+ };
105
+ }
106
+ /**
107
+ * Clear is credential-only: it copies the committed raw speech/cloud subtree
108
+ * and removes only `apiKey`. Submitted nonsecret fields are validated but not
109
+ * applied, and defaults are derived only for the response, never persisted.
110
+ */
111
+ function applyClear(currentConfig) {
112
+ const speech = currentConfig.speechInput;
113
+ if (speech === undefined)
114
+ return { ...currentConfig };
115
+ const nextSpeech = { ...speech };
116
+ if (speech.cloud !== undefined) {
117
+ const nextCloud = { ...speech.cloud };
118
+ delete nextCloud.apiKey;
119
+ nextSpeech.cloud = nextCloud;
120
+ }
121
+ return { ...currentConfig, speechInput: nextSpeech };
122
+ }
123
+ const UPDATE_KEYS = new Set(["expectedRevision", "settings", "credential"]);
124
+ const SETTINGS_KEYS = new Set(["provider", "language", "cloud"]);
125
+ const CLOUD_KEYS = new Set(["baseUrl", "model"]);
126
+ function parseSpeechInputSettingsUpdate(value) {
127
+ const record = requireRecord(value, "Speech input settings update must be an object");
128
+ rejectUnknownKeys(record, UPDATE_KEYS, "Speech input settings update");
129
+ const expectedRevision = record["expectedRevision"];
130
+ if (typeof expectedRevision !== "string" || !isCanonicalLowercaseUuid(expectedRevision)) {
131
+ throw new SpeechInputSettingsValidationError("Speech input settings expectedRevision must be a canonical lowercase UUID");
132
+ }
133
+ return {
134
+ expectedRevision,
135
+ settings: parseSettings(record["settings"]),
136
+ credential: parseCredentialMutation(record["credential"]),
137
+ };
138
+ }
139
+ function parseSettings(value) {
140
+ const record = requireRecord(value, "Speech input settings update settings must be an object");
141
+ rejectUnknownKeys(record, SETTINGS_KEYS, "Speech input settings update settings");
142
+ const provider = record["provider"];
143
+ if (provider !== "auto" && provider !== "browser" && provider !== "cloud") {
144
+ throw new SpeechInputSettingsValidationError("Speech input settings provider must be auto, browser, or cloud");
145
+ }
146
+ let language;
147
+ const languageValue = record["language"];
148
+ if (languageValue !== undefined) {
149
+ if (typeof languageValue !== "string") {
150
+ throw new SpeechInputSettingsValidationError("Speech input settings language must be a canonical BCP 47 language tag");
151
+ }
152
+ const canonical = canonicalBcp47LanguageTag(languageValue);
153
+ if (canonical === undefined || canonical.length > MAX_LANGUAGE_LENGTH) {
154
+ throw new SpeechInputSettingsValidationError("Speech input settings language must be a canonical BCP 47 language tag");
155
+ }
156
+ language = canonical;
157
+ }
158
+ return {
159
+ provider,
160
+ ...(language === undefined ? {} : { language }),
161
+ cloud: parseCloud(record["cloud"]),
162
+ };
163
+ }
164
+ function parseCloud(value) {
165
+ const record = requireRecord(value, "Speech input settings update cloud must be an object");
166
+ rejectUnknownKeys(record, CLOUD_KEYS, "Speech input settings update cloud");
167
+ const baseUrlValue = record["baseUrl"];
168
+ if (typeof baseUrlValue !== "string" || baseUrlValue.trim() === "") {
169
+ throw new SpeechInputSettingsValidationError("Speech input settings cloud base URL must be a non-empty HTTPS URL");
170
+ }
171
+ const baseUrl = baseUrlValue.trim();
172
+ if (baseUrl.length > MAX_BASE_URL_LENGTH) {
173
+ throw new SpeechInputSettingsValidationError("Speech input settings cloud base URL must be at most 2048 characters");
174
+ }
175
+ validateHttpsBaseUrl(baseUrl);
176
+ const modelValue = record["model"];
177
+ if (typeof modelValue !== "string" || modelValue.trim() === "") {
178
+ throw new SpeechInputSettingsValidationError("Speech input settings cloud model must be a non-empty string");
179
+ }
180
+ const model = modelValue.trim();
181
+ if (model.length > MAX_MODEL_LENGTH) {
182
+ throw new SpeechInputSettingsValidationError("Speech input settings cloud model must be at most 256 characters");
183
+ }
184
+ return { baseUrl, model };
185
+ }
186
+ function validateHttpsBaseUrl(value) {
187
+ let url;
188
+ try {
189
+ url = new URL(value);
190
+ }
191
+ catch {
192
+ throw new SpeechInputSettingsValidationError("Speech input settings cloud base URL must be a valid HTTPS URL");
193
+ }
194
+ if (url.protocol !== "https:") {
195
+ throw new SpeechInputSettingsValidationError("Speech input settings cloud base URL must use HTTPS");
196
+ }
197
+ if (url.username !== "" || url.password !== "") {
198
+ throw new SpeechInputSettingsValidationError("Speech input settings cloud base URL must not contain credentials");
199
+ }
200
+ if (url.search !== "") {
201
+ throw new SpeechInputSettingsValidationError("Speech input settings cloud base URL must not contain a query string");
202
+ }
203
+ if (url.hash !== "") {
204
+ throw new SpeechInputSettingsValidationError("Speech input settings cloud base URL must not contain a fragment");
205
+ }
206
+ }
207
+ function parseCredentialMutation(value) {
208
+ const record = requireRecord(value, "Speech input settings update credential must be an object");
209
+ const action = record["action"];
210
+ if (action !== "preserve" && action !== "replace" && action !== "clear") {
211
+ throw new SpeechInputSettingsValidationError("Speech input settings credential action must be preserve, replace, or clear");
212
+ }
213
+ const keys = Object.keys(record);
214
+ if (action === "preserve" || action === "clear") {
215
+ if (keys.length !== 1) {
216
+ throw new SpeechInputSettingsValidationError(`Speech input settings credential ${action} must not include extra fields`);
217
+ }
218
+ return { action };
219
+ }
220
+ if (keys.length !== 2 || !Object.prototype.hasOwnProperty.call(record, "value")) {
221
+ throw new SpeechInputSettingsValidationError("Speech input settings credential replace must include exactly one value");
222
+ }
223
+ const credentialValue = record["value"];
224
+ if (typeof credentialValue !== "string" || credentialValue.trim() === "") {
225
+ throw new SpeechInputSettingsValidationError("Speech input settings credential replace value must be a nonblank string");
226
+ }
227
+ if (Buffer.byteLength(credentialValue, "utf8") > MAX_CREDENTIAL_SOURCE_BYTES) {
228
+ throw new SpeechInputSettingsValidationError("Speech input settings credential replace value must be at most 8 KiB of UTF-8 text");
229
+ }
230
+ // The exact submitted source is stored byte-for-byte; never normalized.
231
+ return { action: "replace", value: credentialValue };
232
+ }
233
+ function requireRecord(value, message) {
234
+ if (!isRecord(value))
235
+ throw new SpeechInputSettingsValidationError(message);
236
+ return value;
237
+ }
238
+ function rejectUnknownKeys(record, allowed, field) {
239
+ const unknownKey = Object.keys(record).find((key) => !allowed.has(key));
240
+ if (unknownKey !== undefined) {
241
+ throw new SpeechInputSettingsValidationError(`${field} contains unknown key ${JSON.stringify(unknownKey)}`);
242
+ }
243
+ }
244
+ function isRecord(value) {
245
+ return typeof value === "object" && value !== null && !Array.isArray(value);
246
+ }
247
+ //# sourceMappingURL=speechInputSettingsService.js.map