arcane-os 0.5.16 → 0.5.18

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 (190) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +42 -19
  3. package/bin/arcane-test.mjs +16 -13
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +41 -38
  5. package/browser-runtime/ai/browser-speech-providers.mjs +53 -46
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +127 -124
  7. package/browser-runtime/ai/browser-wasm.mjs +6 -3
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +26 -23
  9. package/browser-runtime/ai/model-controller.mjs +94 -91
  10. package/browser-runtime/ai/speech-worker-client.mjs +12 -9
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +56 -53
  12. package/browser-runtime/dependencies/strong-type/index.js +207 -82
  13. package/browser-runtime/dependencies/strong-type/package.json +15 -6
  14. package/browser-runtime/dom-event-instrumentation.mjs +24 -20
  15. package/browser-runtime/event-manager.mjs +111 -108
  16. package/browser-runtime/speech-text.mjs +35 -0
  17. package/docs/architecture.md +1 -1
  18. package/docs/reference/README.md +8 -8
  19. package/docs/reference/ai/browser-speech.md +230 -16
  20. package/docs/reference/ai/twin-cloud.md +1 -1
  21. package/docs/reference/availability-and-normalization.md +42 -1
  22. package/docs/reference/cli.md +1 -1
  23. package/docs/reference/core/arcane-ai-contracts.md +1 -1
  24. package/docs/reference/inventory/package-api.json +60 -4
  25. package/docs/reference/inventory/runtime-components.json +2 -2
  26. package/docs/reference/inventory/runtime-modules.json +29 -31
  27. package/docs/reference/protocols.md +17 -13
  28. package/docs/reference/runtime-components.md +6 -5
  29. package/docs/reference/runtime-modules.md +212 -70
  30. package/docs/reference/sdk-api.md +197 -11
  31. package/examples/wasm-ai-demo/index.html +1 -0
  32. package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
  33. package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
  34. package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
  35. package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
  36. package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
  37. package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
  38. package/node_modules/strong-type/README.md +42 -24
  39. package/node_modules/strong-type/benchmark/core.js +324 -0
  40. package/node_modules/strong-type/index.js +207 -82
  41. package/node_modules/strong-type/package.json +14 -6
  42. package/package.json +4 -3
  43. package/runtime/arcane/components/app-bar.html +5 -2
  44. package/runtime/arcane/components/assistant-panel.html +5 -2
  45. package/runtime/arcane/components/chart.html +15 -12
  46. package/runtime/arcane/components/chat.html +118 -141
  47. package/runtime/arcane/components/dashboard-config.html +6 -3
  48. package/runtime/arcane/components/data-view.html +4 -1
  49. package/runtime/arcane/components/directory-picker.html +5 -2
  50. package/runtime/arcane/components/document-inspector.html +6 -3
  51. package/runtime/arcane/components/file-drop.html +5 -2
  52. package/runtime/arcane/components/file-inspector.html +9 -6
  53. package/runtime/arcane/components/file-manager.html +16 -13
  54. package/runtime/arcane/components/local-ai-status.html +5 -2
  55. package/runtime/arcane/components/markdown-document.html +14 -11
  56. package/runtime/arcane/components/markdown-editor.html +4 -1
  57. package/runtime/arcane/components/modal.html +4 -1
  58. package/runtime/arcane/components/output-panel.html +6 -3
  59. package/runtime/arcane/components/preferences-form.html +4 -1
  60. package/runtime/arcane/components/record-timeline.html +8 -5
  61. package/runtime/arcane/components/relationship-board.html +9 -6
  62. package/runtime/arcane/components/source-code-viewer.html +12 -9
  63. package/runtime/arcane/components/source-explanation.html +8 -5
  64. package/runtime/arcane/components/speech.html +24 -21
  65. package/runtime/arcane/components/summary-strip.html +4 -1
  66. package/runtime/arcane/components/task-progress.html +6 -3
  67. package/runtime/arcane/components/terminal-workspace.html +4 -1
  68. package/runtime/arcane/components/voice-transcription.html +11 -8
  69. package/runtime/arcane/components/web-navigator.html +5 -2
  70. package/runtime/arcane/entities/ApiModelRecord.js +5 -2
  71. package/runtime/arcane/entities/Calculation.js +5 -2
  72. package/runtime/arcane/entities/Chat.js +33 -33
  73. package/runtime/arcane/entities/CommunicationMessage.js +4 -1
  74. package/runtime/arcane/entities/CommunicationThread.js +4 -1
  75. package/runtime/arcane/entities/File.js +1 -1
  76. package/runtime/arcane/entities/Image.js +8 -5
  77. package/runtime/arcane/entities/IntentEnvelope.js +20 -17
  78. package/runtime/arcane/entities/Preference.js +9 -6
  79. package/runtime/arcane/entities/Theme.js +5 -2
  80. package/runtime/arcane/entities/User.js +11 -11
  81. package/runtime/arcane/entities/Weather.js +5 -2
  82. package/runtime/arcane/modules/AI.js +503 -187
  83. package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
  84. package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
  85. package/runtime/arcane/modules/AIProviderRuntime.js +117 -92
  86. package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
  87. package/runtime/arcane/modules/AIRuntimeState.js +29 -26
  88. package/runtime/arcane/modules/AnsiText.js +4 -1
  89. package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
  90. package/runtime/arcane/modules/AppDataScope.js +8 -5
  91. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
  92. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
  93. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
  94. package/runtime/arcane/modules/AsyncBoundary.js +13 -10
  95. package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
  96. package/runtime/arcane/modules/CalculatorEngine.js +5 -2
  97. package/runtime/arcane/modules/ChatRecords.js +18 -15
  98. package/runtime/arcane/modules/CommunicationAppController.js +9 -6
  99. package/runtime/arcane/modules/CommunicationHub.js +9 -6
  100. package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
  101. package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
  102. package/runtime/arcane/modules/ComponentContracts.js +56 -53
  103. package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
  104. package/runtime/arcane/modules/ConversationActionItems.js +15 -12
  105. package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
  106. package/runtime/arcane/modules/ConversationTimebox.js +28 -25
  107. package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
  108. package/runtime/arcane/modules/DBLS.js +7 -4
  109. package/runtime/arcane/modules/DBOPFS.js +7 -7
  110. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
  111. package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
  112. package/runtime/arcane/modules/DirectoryPicker.js +8 -5
  113. package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
  114. package/runtime/arcane/modules/DocumentNavigation.js +7 -4
  115. package/runtime/arcane/modules/Errors.js +28 -25
  116. package/runtime/arcane/modules/HTMLImport.js +7 -4
  117. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
  118. package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
  119. package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
  120. package/runtime/arcane/modules/MD.js +1 -1
  121. package/runtime/arcane/modules/Mail.js +46 -43
  122. package/runtime/arcane/modules/MailOutbox.mjs +48 -45
  123. package/runtime/arcane/modules/MailTransport.mjs +29 -26
  124. package/runtime/arcane/modules/MarkdownSpeech.js +1 -50
  125. package/runtime/arcane/modules/MemoryRecords.js +7 -4
  126. package/runtime/arcane/modules/MessageAdvisory.js +6 -3
  127. package/runtime/arcane/modules/ModelDefinition.js +7 -4
  128. package/runtime/arcane/modules/Ollama.js +6 -3
  129. package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
  130. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
  131. package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
  132. package/runtime/arcane/modules/PreferenceStore.js +20 -17
  133. package/runtime/arcane/modules/PreparedSpeech.js +485 -0
  134. package/runtime/arcane/modules/Questionnaire.js +6 -3
  135. package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
  136. package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
  137. package/runtime/arcane/modules/RecordReviewStore.js +12 -9
  138. package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
  139. package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
  140. package/runtime/arcane/modules/ScreenCapture.js +20 -17
  141. package/runtime/arcane/modules/SpeechPlayback.js +61 -28
  142. package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
  143. package/runtime/arcane/modules/SystemAppearance.js +5 -2
  144. package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
  145. package/runtime/arcane/modules/TerminalClient.js +14 -11
  146. package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
  147. package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
  148. package/runtime/arcane/modules/ThemeManager.js +5 -2
  149. package/runtime/arcane/modules/TimeGuard.js +1 -1
  150. package/runtime/arcane/modules/ToolCallRouter.js +11 -8
  151. package/runtime/arcane/modules/WaitForComponent.js +19 -16
  152. package/runtime/arcane/modules/YouTubeMedia.js +4 -1
  153. package/runtime/strong-type/index.js +1276 -352
  154. package/runtime/strong-type/package.json +69 -45
  155. package/src/app-descriptor.mjs +17 -14
  156. package/src/application-tests.mjs +4 -1
  157. package/src/cli/main.mjs +8 -5
  158. package/src/constants.mjs +4 -1
  159. package/src/dev-server.mjs +8 -5
  160. package/src/doctor.mjs +5 -2
  161. package/src/dom-event-instrumentation.mjs +24 -20
  162. package/src/errors.mjs +7 -3
  163. package/src/event-manager.mjs +111 -108
  164. package/src/event-queue.mjs +8 -5
  165. package/src/events.mjs +12 -9
  166. package/src/import-map.mjs +36 -27
  167. package/src/installed-sdk-runtime.mjs +4 -1
  168. package/src/integrated-provider-loader.mjs +9 -6
  169. package/src/mail-credentials.mjs +16 -13
  170. package/src/mail-server.mjs +53 -50
  171. package/src/mail.mjs +18 -15
  172. package/src/native-plan.mjs +12 -9
  173. package/src/native-provider-loader.mjs +7 -4
  174. package/src/packager/core.mjs +18 -15
  175. package/src/process.mjs +6 -3
  176. package/src/release-bundle.mjs +14 -11
  177. package/src/runtime.mjs +4 -1
  178. package/src/scaffold.mjs +9 -6
  179. package/src/sdk-browser-runtime.mjs +4 -1
  180. package/src/source-server.mjs +17 -14
  181. package/src/targets/index.mjs +5 -2
  182. package/src/templates/workspace-template.mjs +13 -6
  183. package/src/testing-loader.mjs +7 -4
  184. package/src/testing.mjs +10 -7
  185. package/src/toolchain.mjs +13 -10
  186. package/src/update-check.mjs +9 -6
  187. package/src/workspace-operation-lock.mjs +14 -11
  188. package/src/workspace-runtime.mjs +4 -1
  189. package/src/workspace.mjs +17 -14
  190. package/runtime/arcane/modules/AIResponseLength.js +0 -32
@@ -1,3 +1,4 @@
1
+ import Is from "../dependencies/strong-type/index.js";
1
2
  import {
2
3
  createBrowserSpeechAuthority,
3
4
  isBrowserSpeechArtifactGraph,
@@ -5,6 +6,9 @@ import {
5
6
  isBrowserSpeechAuthority,
6
7
  isDbopfsSpeechArtifactStore,
7
8
  } from "./browser-speech-artifacts.mjs";
9
+ import { stripSpeechFormatting } from "../speech-text.mjs";
10
+
11
+ const is = new Is(false);
8
12
 
9
13
  const completeValue = (value) => value;
10
14
  import {
@@ -45,7 +49,7 @@ function providerError(code, message, cause, reason) {
45
49
  ? "AbortError"
46
50
  : "ArcaneBrowserSpeechProviderError";
47
51
  error.code = code;
48
- error.reason = typeof reason === "string" && reason
52
+ error.reason = is.string(reason) && reason
49
53
  ? reason
50
54
  : UNMAPPED_PROVIDER_REASON;
51
55
  PROVIDER_ERRORS.add(error);
@@ -53,7 +57,7 @@ function providerError(code, message, cause, reason) {
53
57
  }
54
58
 
55
59
  function isBrowserSpeechProviderError(value) {
56
- return typeof value === "object"
60
+ return is.object(value)
57
61
  && value !== null
58
62
  && PROVIDER_ERRORS.has(value);
59
63
  }
@@ -68,7 +72,7 @@ function trustedLoadFailure(error, role) {
68
72
  if (isTrustedSpeechError(error)) return error;
69
73
  return providerError(
70
74
  "ARCANE_AI_PROVIDER_LOAD_FAILED",
71
- typeof error?.message === "string" && error.message
75
+ is.string(error?.message) && error.message
72
76
  ? error.message
73
77
  : `The browser ${role} provider load was rejected.`,
74
78
  error,
@@ -80,7 +84,7 @@ function trustedRequestFailure(error, role) {
80
84
  if (isTrustedSpeechError(error)) return error;
81
85
  return providerError(
82
86
  "ARCANE_AI_PROVIDER_REQUEST_FAILED",
83
- typeof error?.message === "string" && error.message
87
+ is.string(error?.message) && error.message
84
88
  ? error.message
85
89
  : `The browser ${role} engine operation was rejected.`,
86
90
  error,
@@ -94,7 +98,7 @@ function trustedWorkerFailure(error, role) {
94
98
  if (isTrustedSpeechError(error)) return error;
95
99
  return providerError(
96
100
  "ARCANE_AI_WORKER_MESSAGE_REJECTED",
97
- typeof error?.message === "string" && error.message
101
+ is.string(error?.message) && error.message
98
102
  ? error.message
99
103
  : `The browser ${role} Worker returned an unreadable error envelope.`,
100
104
  error,
@@ -106,7 +110,7 @@ function trustedLifecycleFailure(error, role, operation) {
106
110
  if (isTrustedSpeechError(error)) return error;
107
111
  return providerError(
108
112
  "ARCANE_AI_INVALID_REQUEST",
109
- typeof error?.message === "string" && error.message
113
+ is.string(error?.message) && error.message
110
114
  ? error.message
111
115
  : `The browser ${role} ${operation} context could not be read.`,
112
116
  error,
@@ -115,8 +119,8 @@ function trustedLifecycleFailure(error, role, operation) {
115
119
  }
116
120
 
117
121
  function resolveReason(reason, fallback = UNMAPPED_PROVIDER_REASON) {
118
- const resolved = typeof reason === "function" ? reason() : reason;
119
- return typeof resolved === "string" && resolved ? resolved : fallback;
122
+ const resolved = is.function(reason) ? reason() : reason;
123
+ return is.string(resolved) && resolved ? resolved : fallback;
120
124
  }
121
125
 
122
126
  function abortError(signal, reason = UNMAPPED_PROVIDER_REASON) {
@@ -139,7 +143,7 @@ function workerFailureCode(error) {
139
143
  }
140
144
 
141
145
  function requiredIdentifier(value, label) {
142
- if (typeof value !== "string" || !value.trim()) {
146
+ if (!is.string(value) || !value.trim()) {
143
147
  throw new TypeError(`${label} must be a nonempty trimmed string.`);
144
148
  }
145
149
  return value.trim();
@@ -158,7 +162,7 @@ function normalizeSpeechExecution(role, execution) {
158
162
  maxConcurrentRequests: DEFAULT_TTS_MAX_CONCURRENT_REQUESTS,
159
163
  });
160
164
  }
161
- if (!execution || typeof execution !== "object" || Array.isArray(execution)) {
165
+ if (!execution || !is.object(execution) || is.array(execution)) {
162
166
  throw new TypeError("Browser Kokoro execution must be a plain data record.");
163
167
  }
164
168
  const prototype = Object.getPrototypeOf(execution);
@@ -184,7 +188,7 @@ function normalizeSpeechExecution(role, execution) {
184
188
  throw new TypeError('Browser Kokoro execution.device must be "auto", "webgpu", or "wasm".');
185
189
  }
186
190
  if (
187
- !Number.isSafeInteger(maxConcurrentRequests)
191
+ !is.safeInteger(maxConcurrentRequests)
188
192
  || maxConcurrentRequests < 1
189
193
  || maxConcurrentRequests > MAX_TTS_CONCURRENT_REQUESTS
190
194
  ) {
@@ -319,15 +323,15 @@ function linkedAbortFailure(linked, fallbackReason) {
319
323
  function isAbortSignal(value) {
320
324
  return value === null
321
325
  || value === undefined
322
- || (typeof value === "object"
323
- && typeof value.aborted === "boolean"
324
- && typeof value.addEventListener === "function"
325
- && typeof value.removeEventListener === "function");
326
+ || (is.object(value)
327
+ && is.boolean(value.aborted)
328
+ && is.function(value.addEventListener)
329
+ && is.function(value.removeEventListener));
326
330
  }
327
331
 
328
332
  function providerContext(context, role, operation) {
329
333
  if (context === undefined) return completeValue({ signal: null });
330
- if (!context || typeof context !== "object" || Array.isArray(context)) {
334
+ if (!context || !is.object(context) || is.array(context)) {
331
335
  throw providerError(
332
336
  "ARCANE_AI_INVALID_REQUEST",
333
337
  `Browser ${role} ${operation} context must be an object.`,
@@ -470,7 +474,7 @@ function genericPayloadDescriptors(
470
474
  }
471
475
  const descriptors = Object.getOwnPropertyDescriptors(payload);
472
476
  for (const key of Reflect.ownKeys(descriptors)) {
473
- if (typeof key === "symbol" || !allowedKeys.includes(key)) {
477
+ if (is.symbol(key) || !allowedKeys.includes(key)) {
474
478
  throw providerError(
475
479
  "ARCANE_AI_INVALID_REQUEST",
476
480
  `${label} contains an unknown field.`,
@@ -501,7 +505,7 @@ function genericPayloadDescriptors(
501
505
  }
502
506
 
503
507
  function audioMimeEssence(value, label, reasonPrefix) {
504
- if (typeof value !== "string") {
508
+ if (!is.string(value)) {
505
509
  throw providerError(
506
510
  "ARCANE_AI_INVALID_REQUEST",
507
511
  `${label} must be an audio MIME type.`,
@@ -585,7 +589,7 @@ function observeLoadOperation(record, { signal, progress }, role) {
585
589
  };
586
590
  const observer = completeValue({
587
591
  progress(value) {
588
- if (settled || typeof progress !== "function") return;
592
+ if (settled || !is.function(progress)) return;
589
593
  try {
590
594
  progress(value);
591
595
  } catch (error) {
@@ -641,7 +645,7 @@ async function decodeSharedTranscriptionPayload(
641
645
  operationSubject: "stt-transcription",
642
646
  });
643
647
  const audio = descriptors.audio.value;
644
- if (typeof Blob !== "function") {
648
+ if (!is.function(globalThis.Blob)) {
645
649
  throw providerError(
646
650
  "ARCANE_AI_INVALID_REQUEST",
647
651
  "Shared speech transcription requires the browser Blob constructor.",
@@ -649,7 +653,7 @@ async function decodeSharedTranscriptionPayload(
649
653
  "stt-transcription-blob-constructor-unavailable",
650
654
  );
651
655
  }
652
- if (!(audio instanceof Blob)) {
656
+ if (!is.instanceCheck(audio, Blob)) {
653
657
  throw providerError(
654
658
  "ARCANE_AI_INVALID_REQUEST",
655
659
  "Shared speech transcription audio must be a Blob or File.",
@@ -684,7 +688,7 @@ async function decodeSharedTranscriptionPayload(
684
688
  }
685
689
  const OfflineAudioContext = globalThis.OfflineAudioContext
686
690
  ?? globalThis.webkitOfflineAudioContext;
687
- if (typeof OfflineAudioContext !== "function") {
691
+ if (!is.function(OfflineAudioContext)) {
688
692
  throw providerError(
689
693
  "ARCANE_AI_AUDIO_DECODE_UNAVAILABLE",
690
694
  `Shared Blob transcription requires OfflineAudioContext decoding at ${sampleRate} Hz.`,
@@ -703,7 +707,7 @@ async function decodeSharedTranscriptionPayload(
703
707
  "stt-browser-offline-audio-context-construction-rejected",
704
708
  );
705
709
  }
706
- if (typeof decoder.decodeAudioData !== "function") {
710
+ if (!is.function(decoder.decodeAudioData)) {
707
711
  throw providerError(
708
712
  "ARCANE_AI_AUDIO_DECODE_UNAVAILABLE",
709
713
  "Shared Blob transcription requires browser audio decoding.",
@@ -734,7 +738,7 @@ async function decodeSharedTranscriptionPayload(
734
738
  "stt-browser-audio-decode-operation-rejected",
735
739
  );
736
740
  }
737
- if (!decoded || typeof decoded !== "object") {
741
+ if (!decoded || !is.object(decoded)) {
738
742
  throw providerError(
739
743
  "ARCANE_AI_AUDIO_DECODE_FAILED",
740
744
  "Decoded speech audio must be an AudioBuffer-like object.",
@@ -750,7 +754,7 @@ async function decodeSharedTranscriptionPayload(
750
754
  "stt-browser-decoded-audio-sample-rate-mismatch",
751
755
  );
752
756
  }
753
- if (!Number.isSafeInteger(decoded.length)) {
757
+ if (!is.safeInteger(decoded.length)) {
754
758
  throw providerError(
755
759
  "ARCANE_AI_AUDIO_DECODE_FAILED",
756
760
  "Decoded speech audio frame length must be a safe integer.",
@@ -766,7 +770,7 @@ async function decodeSharedTranscriptionPayload(
766
770
  "stt-browser-decoded-audio-empty",
767
771
  );
768
772
  }
769
- if (!Number.isSafeInteger(decoded.numberOfChannels)) {
773
+ if (!is.safeInteger(decoded.numberOfChannels)) {
770
774
  throw providerError(
771
775
  "ARCANE_AI_AUDIO_DECODE_FAILED",
772
776
  "Decoded speech audio channel count must be a safe integer.",
@@ -782,7 +786,7 @@ async function decodeSharedTranscriptionPayload(
782
786
  "stt-browser-decoded-audio-channel-count-zero",
783
787
  );
784
788
  }
785
- if (typeof decoded.getChannelData !== "function") {
789
+ if (!is.function(decoded.getChannelData)) {
786
790
  throw providerError(
787
791
  "ARCANE_AI_AUDIO_DECODE_FAILED",
788
792
  "Decoded speech audio must expose getChannelData().",
@@ -803,7 +807,7 @@ async function decodeSharedTranscriptionPayload(
803
807
  "stt-browser-decoded-audio-channel-read-rejected",
804
808
  );
805
809
  }
806
- if (!(channel instanceof Float32Array)) {
810
+ if (!is.instanceCheck(channel, Float32Array)) {
807
811
  throw providerError(
808
812
  "ARCANE_AI_AUDIO_DECODE_FAILED",
809
813
  "Decoded speech audio channels must be Float32Array values.",
@@ -826,7 +830,7 @@ async function decodeSharedTranscriptionPayload(
826
830
  let sample = 0;
827
831
  for (const channel of channels) sample += channel[index];
828
832
  sample /= channels.length;
829
- if (!Number.isFinite(sample)) {
833
+ if (!is.finite(sample)) {
830
834
  throw providerError(
831
835
  "ARCANE_AI_AUDIO_DECODE_FAILED",
832
836
  "Decoded speech audio contains a non-finite sample.",
@@ -853,7 +857,7 @@ function cloneNativeTranscriptionPayload(payload, authority) {
853
857
  );
854
858
  assertPayloadModel(payload, authority, { operationSubject: "stt-transcription" });
855
859
  const sampleRate = inputSampleRate(authority);
856
- if (!(descriptors.audio.value instanceof Float32Array)) {
860
+ if (!is.instanceCheck(descriptors.audio.value, Float32Array)) {
857
861
  throw providerError(
858
862
  "ARCANE_AI_INVALID_REQUEST",
859
863
  "Whisper requires Float32Array audio.",
@@ -878,7 +882,7 @@ function cloneNativeTranscriptionPayload(payload, authority) {
878
882
  );
879
883
  }
880
884
  for (const sample of descriptors.audio.value) {
881
- if (!Number.isFinite(sample)) {
885
+ if (!is.finite(sample)) {
882
886
  throw providerError(
883
887
  "ARCANE_AI_INVALID_REQUEST",
884
888
  "Whisper audio must contain only finite Float32 PCM samples.",
@@ -896,7 +900,7 @@ function cloneNativeTranscriptionPayload(payload, authority) {
896
900
  });
897
901
  }
898
902
 
899
- function normalizeSynthesisPayload(payload, authority) {
903
+ function normalizeSynthesisPayload(payload, authority, speechInputPrepared) {
900
904
  const shared = Object.hasOwn(payload, "input");
901
905
  let descriptors;
902
906
  let textValue;
@@ -932,11 +936,12 @@ function normalizeSynthesisPayload(payload, authority) {
932
936
  assertPayloadModel(payload, authority, { operationSubject: "tts-synthesis" });
933
937
  textValue = descriptors.text.value;
934
938
  }
935
- const text = typeof textValue === "string" ? textValue : "";
939
+ const suppliedText = is.string(textValue) ? textValue : "";
940
+ const text = speechInputPrepared === true ? suppliedText : stripSpeechFormatting(suppliedText);
936
941
  const voiceValue = Object.hasOwn(descriptors, "voice")
937
942
  ? descriptors.voice.value
938
943
  : authority.defaultVoice;
939
- const voice = typeof voiceValue === "string" ? voiceValue : "";
944
+ const voice = is.string(voiceValue) ? voiceValue : "";
940
945
  const speed = Object.hasOwn(descriptors, "speed") ? descriptors.speed.value : 1;
941
946
  if (!text.trim()) {
942
947
  throw providerError(
@@ -954,7 +959,7 @@ function normalizeSynthesisPayload(payload, authority) {
954
959
  "tts-synthesis-voice-empty",
955
960
  );
956
961
  }
957
- if (!Number.isFinite(speed) || speed <= 0) {
962
+ if (!is.finite(speed) || speed <= 0) {
958
963
  throw providerError(
959
964
  "ARCANE_AI_INVALID_REQUEST",
960
965
  "Kokoro speed must be greater than 0.",
@@ -983,8 +988,9 @@ async function normalizeRequestPayload(
983
988
  authority,
984
989
  signal,
985
990
  cancellationReason,
991
+ speechInputPrepared,
986
992
  ) {
987
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
993
+ if (!payload || !is.object(payload) || is.array(payload)) {
988
994
  throw providerError(
989
995
  "ARCANE_AI_INVALID_REQUEST",
990
996
  "Speech request payload must be an object.",
@@ -998,8 +1004,8 @@ async function normalizeRequestPayload(
998
1004
  const audio = Object.getOwnPropertyDescriptor(payload, "audio");
999
1005
  if (Object.hasOwn(payload, "mimeType")
1000
1006
  || (Object.hasOwn(audio ?? {}, "value")
1001
- && typeof Blob === "function"
1002
- && audio.value instanceof Blob)) {
1007
+ && is.function(globalThis.Blob)
1008
+ && is.instanceCheck(audio.value, Blob))) {
1003
1009
  return decodeSharedTranscriptionPayload(
1004
1010
  payload,
1005
1011
  authority,
@@ -1009,12 +1015,12 @@ async function normalizeRequestPayload(
1009
1015
  }
1010
1016
  return cloneNativeTranscriptionPayload(payload, authority);
1011
1017
  }
1012
- return normalizeSynthesisPayload(payload, authority);
1018
+ return normalizeSynthesisPayload(payload, authority, speechInputPrepared);
1013
1019
  }
1014
1020
 
1015
1021
  function encodeSharedSynthesisResult(result, authority) {
1016
1022
  const sampleRate = outputSampleRate(authority);
1017
- if (!result || typeof result !== "object") {
1023
+ if (!result || !is.object(result)) {
1018
1024
  throw providerError(
1019
1025
  "ARCANE_AI_INVALID_PROVIDER_RESULT",
1020
1026
  "Browser Kokoro must return a synthesis result object.",
@@ -1022,7 +1028,7 @@ function encodeSharedSynthesisResult(result, authority) {
1022
1028
  "tts-synthesis-result-not-object",
1023
1029
  );
1024
1030
  }
1025
- if (!(result.audio instanceof Float32Array)) {
1031
+ if (!is.instanceCheck(result.audio, Float32Array)) {
1026
1032
  throw providerError(
1027
1033
  "ARCANE_AI_INVALID_PROVIDER_RESULT",
1028
1034
  "Browser Kokoro must return Float32 PCM audio.",
@@ -1077,7 +1083,7 @@ function encodeSharedSynthesisResult(result, authority) {
1077
1083
  view.setUint32(40, result.audio.length * 2, true);
1078
1084
  for (let index = 0; index < result.audio.length; index += 1) {
1079
1085
  const sample = result.audio[index];
1080
- if (!Number.isFinite(sample)) {
1086
+ if (!is.finite(sample)) {
1081
1087
  throw providerError(
1082
1088
  "ARCANE_AI_INVALID_PROVIDER_RESULT",
1083
1089
  "Browser Kokoro returned a non-finite PCM sample.",
@@ -1112,7 +1118,7 @@ function createBrowserSpeechProvider({
1112
1118
  if (localOnly !== true) {
1113
1119
  throw new TypeError("Browser speech providers are localOnly.");
1114
1120
  }
1115
- if (typeof offline !== "boolean") {
1121
+ if (!is.boolean(offline)) {
1116
1122
  throw new TypeError("Browser speech offline must be a boolean.");
1117
1123
  }
1118
1124
  if (!isDbopfsSpeechArtifactStore(store)) {
@@ -1443,7 +1449,7 @@ function createBrowserSpeechProvider({
1443
1449
  } catch (error) {
1444
1450
  return Promise.reject(trustedLoadFailure(error, role));
1445
1451
  }
1446
- if (loadProgress !== undefined && typeof loadProgress !== "function") {
1452
+ if (loadProgress !== undefined && !is.function(loadProgress)) {
1447
1453
  return Promise.reject(providerError(
1448
1454
  "ARCANE_AI_INVALID_REQUEST",
1449
1455
  "Browser speech load progress must be a function when supplied.",
@@ -1511,7 +1517,7 @@ function createBrowserSpeechProvider({
1511
1517
  prepared,
1512
1518
  released: false,
1513
1519
  };
1514
- record.warnings = Array.isArray(prepared.warnings)
1520
+ record.warnings = is.array(prepared.warnings)
1515
1521
  ? completeValue([...prepared.warnings])
1516
1522
  : NO_PROVIDER_WARNINGS;
1517
1523
  lastWarnings = record.warnings;
@@ -1619,7 +1625,7 @@ function createBrowserSpeechProvider({
1619
1625
  }, role);
1620
1626
  },
1621
1627
 
1622
- async request(context = {}) {
1628
+ async request(context = {}, { speechInputPrepared = false } = {}) {
1623
1629
  let requestContext;
1624
1630
  try {
1625
1631
  requestContext = providerContext(context, role, "request");
@@ -1713,6 +1719,7 @@ function createBrowserSpeechProvider({
1713
1719
  authority,
1714
1720
  linked.controller.signal,
1715
1721
  browserSpeechRequestAbortReason,
1722
+ speechInputPrepared,
1716
1723
  );
1717
1724
  throwIfAborted(
1718
1725
  linked.controller.signal,