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,6 +1,9 @@
1
+ import Is from "../dependencies/strong-type/index.js";
1
2
  import { arcaneLogging } from '../logging.mjs';
2
3
  import { createArcaneEventSource } from "arcane-os/event-manager";
3
4
 
5
+ const is = new Is(false);
6
+
4
7
  export const ARCANE_AI_ADAPTER_PROTOCOL = "arcane-ai-adapter/1";
5
8
 
6
9
  const MODEL_CONTROLLER_EVENT_TYPES = ["statechange", "progress"];
@@ -65,18 +68,18 @@ export function normalizeArcaneAIError(error, {
65
68
  { cause: error ?? signal?.reason, kind, operation },
66
69
  );
67
70
  }
68
- if (error instanceof ArcaneAIError) return error;
69
- const code = typeof error?.code === "string" && error.code.startsWith("ARCANE_AI_")
71
+ if (is.instanceCheck(error, ArcaneAIError)) return error;
72
+ const code = is.string(error?.code) && error.code.startsWith("ARCANE_AI_")
70
73
  ? error.code
71
74
  : ERROR_CODES[operation] ?? ERROR_CODES.request;
72
- const message = typeof error?.message === "string" && error.message.trim()
75
+ const message = is.string(error?.message) && error.message.trim()
73
76
  ? error.message
74
77
  : `The Arcane AI ${operation} operation failed.`;
75
78
  return new ArcaneAIError(code, message, { cause: error, kind, operation });
76
79
  }
77
80
 
78
81
  function providerMethod(provider, name) {
79
- return typeof provider?.[name] === "function" ? provider[name].bind(provider) : null;
82
+ return is.function(provider?.[name]) ? provider[name].bind(provider) : null;
80
83
  }
81
84
 
82
85
  function invalidStatus(cause) {
@@ -91,7 +94,7 @@ function copyError(error) {
91
94
  if (!error) return null;
92
95
  let code;
93
96
  let message;
94
- if (typeof error === "object" || typeof error === "function") {
97
+ if (is.object(error) || is.function(error)) {
95
98
  try {
96
99
  const codeDescriptor = Object.getOwnPropertyDescriptor(error, "code");
97
100
  const messageDescriptor = Object.getOwnPropertyDescriptor(error, "message");
@@ -102,7 +105,7 @@ function copyError(error) {
102
105
  code = codeDescriptor?.value;
103
106
  message = messageDescriptor?.value;
104
107
  } catch (copyErrorFailure) {
105
- if (copyErrorFailure instanceof ArcaneAIError) throw copyErrorFailure;
108
+ if (is.instanceCheck(copyErrorFailure, ArcaneAIError)) throw copyErrorFailure;
106
109
  throw invalidStatus(copyErrorFailure);
107
110
  }
108
111
  }
@@ -113,17 +116,17 @@ function copyError(error) {
113
116
  }
114
117
 
115
118
  function isModelControllerListener(value) {
116
- return typeof value === "function"
117
- || Boolean(value && typeof value === "object" && typeof value.handleEvent === "function");
119
+ return is.function(value)
120
+ || Boolean(value && is.object(value) && is.function(value.handleEvent));
118
121
  }
119
122
 
120
123
  function copyProviderStatus(value) {
121
124
  if (value === undefined || value === null) return {};
122
- if (typeof value !== "object" || Array.isArray(value)) throw invalidStatus();
125
+ if (!is.object(value) || is.array(value)) throw invalidStatus();
123
126
  try {
124
127
  const copy = {};
125
128
  for (const key of Reflect.ownKeys(value)) {
126
- if (typeof key !== "string") throw invalidStatus();
129
+ if (!is.string(key)) throw invalidStatus();
127
130
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
128
131
  if (!descriptor || !("value" in descriptor)) throw invalidStatus();
129
132
  Object.defineProperty(copy, key, {
@@ -135,7 +138,7 @@ function copyProviderStatus(value) {
135
138
  }
136
139
  return copy;
137
140
  } catch (error) {
138
- if (error instanceof ArcaneAIError) throw error;
141
+ if (is.instanceCheck(error, ArcaneAIError)) throw error;
139
142
  throw invalidStatus(error);
140
143
  }
141
144
  }
@@ -149,20 +152,20 @@ function invalidProgress(cause) {
149
152
  }
150
153
 
151
154
  function copyProgressValue(value, state) {
152
- if (value === null || typeof value !== "object") return value;
155
+ if (value === null || !is.object(value)) return value;
153
156
  if (state.seen.has(value)) {
154
157
  throw invalidProgress();
155
158
  }
156
159
  const prototype = Object.getPrototypeOf(value);
157
- if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
160
+ if (!is.array(value) && prototype !== Object.prototype && prototype !== null) {
158
161
  throw invalidProgress();
159
162
  }
160
163
  state.seen.add(value);
161
- const copy = Array.isArray(value) ? [] : prototype === null ? Object.create(null) : {};
164
+ const copy = is.array(value) ? [] : prototype === null ? Object.create(null) : {};
162
165
  try {
163
166
  for (const key of Reflect.ownKeys(value)) {
164
- if (Array.isArray(value) && key === "length") continue;
165
- if (typeof key !== "string") {
167
+ if (is.array(value) && key === "length") continue;
168
+ if (!is.string(key)) {
166
169
  throw invalidProgress();
167
170
  }
168
171
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
@@ -182,23 +185,23 @@ function copyProgressValue(value, state) {
182
185
 
183
186
  function copyProgress(progress) {
184
187
  if (progress === undefined || progress === null) return null;
185
- if (typeof progress !== "object" || Array.isArray(progress)) {
188
+ if (!is.object(progress) || is.array(progress)) {
186
189
  throw invalidProgress();
187
190
  }
188
191
  try {
189
192
  return copyProgressValue(progress, { seen: new WeakSet() });
190
193
  } catch (error) {
191
- if (error instanceof ArcaneAIError) throw error;
194
+ if (is.instanceCheck(error, ArcaneAIError)) throw error;
192
195
  throw invalidProgress(error);
193
196
  }
194
197
  }
195
198
 
196
199
  function publicProgress(progress) {
197
- return progress && typeof progress === "object" ? copyProgress(progress) : null;
200
+ return progress && is.object(progress) ? copyProgress(progress) : null;
198
201
  }
199
202
 
200
203
  function localRequirement(options, provider) {
201
- if (options?.localOnly !== undefined && typeof options.localOnly !== "boolean") {
204
+ if (options?.localOnly !== undefined && !is.boolean(options.localOnly)) {
202
205
  throw new TypeError("localOnly must be a boolean when provided.");
203
206
  }
204
207
  if (options?.localOnly === true && provider.capabilities?.().localOnly !== true) {
@@ -222,7 +225,7 @@ function linkedAbortSignal(externalSignal) {
222
225
  }
223
226
 
224
227
  function fireAndForget(callback, ...args) {
225
- if (typeof callback !== "function") return;
228
+ if (!is.function(callback)) return;
226
229
  try {
227
230
  Promise.resolve(callback(...args)).catch(() => undefined);
228
231
  } catch {
@@ -245,20 +248,20 @@ function displayRequestId(value) {
245
248
  function completeTextValue(value, seen, location) {
246
249
  if (value === null) return null;
247
250
  if (value === undefined) return { $type: "undefined" };
248
- if (typeof value === "bigint") return { $type: "bigint", value: value.toString() };
249
- if (typeof value === "number" && !Number.isFinite(value)) {
251
+ if (is.bigint(value)) return { $type: "bigint", value: value.toString() };
252
+ if (is.number(value) && !is.finite(value)) {
250
253
  return { $type: "number", value: String(value) };
251
254
  }
252
- if (typeof value === "symbol") return { $type: "symbol", value: String(value) };
253
- if (typeof value === "function") {
255
+ if (is.symbol(value)) return { $type: "symbol", value: String(value) };
256
+ if (is.function(value)) {
254
257
  return { $type: "function", value: Function.prototype.toString.call(value) };
255
258
  }
256
- if (typeof value !== "object") return value;
259
+ if (!is.object(value)) return value;
257
260
  if (seen.has(value)) return { $ref: seen.get(value) };
258
261
  seen.set(value, location);
259
- if (value instanceof Date) return { $type: "date", value: value.toISOString() };
260
- if (value instanceof RegExp) return { $type: "regexp", value: String(value) };
261
- if (value instanceof Map) {
262
+ if (is.instanceCheck(value, Date)) return { $type: "date", value: value.toISOString() };
263
+ if (is.instanceCheck(value, RegExp)) return { $type: "regexp", value: String(value) };
264
+ if (is.instanceCheck(value, Map)) {
262
265
  return {
263
266
  $type: "map",
264
267
  entries: [...value.entries()].map(([key, entry], index) => [
@@ -267,7 +270,7 @@ function completeTextValue(value, seen, location) {
267
270
  ]),
268
271
  };
269
272
  }
270
- if (value instanceof Set) {
273
+ if (is.instanceCheck(value, Set)) {
271
274
  return {
272
275
  $type: "set",
273
276
  values: [...value].map((entry, index) => completeTextValue(
@@ -277,25 +280,25 @@ function completeTextValue(value, seen, location) {
277
280
  )),
278
281
  };
279
282
  }
280
- if (ArrayBuffer.isView(value)) {
283
+ if (is.arrayBufferView(value)) {
281
284
  return {
282
285
  $type: value.constructor?.name ?? "ArrayBufferView",
283
286
  values: Array.from(
284
- value instanceof DataView
287
+ is.instanceCheck(value, DataView)
285
288
  ? new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
286
289
  : value,
287
290
  (entry, index) => completeTextValue(entry, seen, `${location}.values[${index}]`),
288
291
  ),
289
292
  };
290
293
  }
291
- if (value instanceof ArrayBuffer) {
294
+ if (is.instanceCheck(value, ArrayBuffer)) {
292
295
  return { $type: "ArrayBuffer", values: Array.from(new Uint8Array(value)) };
293
296
  }
294
- const copy = Array.isArray(value) ? [] : {};
297
+ const copy = is.array(value) ? [] : {};
295
298
  for (const key of Reflect.ownKeys(value)) {
296
- if (Array.isArray(value) && key === "length") continue;
299
+ if (is.array(value) && key === "length") continue;
297
300
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
298
- const renderedKey = typeof key === "symbol" ? `[${String(key)}]` : key;
301
+ const renderedKey = is.symbol(key) ? `[${String(key)}]` : key;
299
302
  copy[renderedKey] = descriptor && "value" in descriptor
300
303
  ? completeTextValue(descriptor.value, seen, `${location}.${renderedKey}`)
301
304
  : {
@@ -308,12 +311,12 @@ function completeTextValue(value, seen, location) {
308
311
  }
309
312
 
310
313
  export function completeValueText(value) {
311
- if (typeof value === "string") return value;
314
+ if (is.string(value)) return value;
312
315
  return JSON.stringify(completeTextValue(value, new WeakMap(), "$"), null, 2);
313
316
  }
314
317
 
315
318
  function textFromCompletion(completion) {
316
- if (!Array.isArray(completion?.choices)) return completeValueText(completion);
319
+ if (!is.array(completion?.choices)) return completeValueText(completion);
317
320
  if(completion.choices.length>1)return completeValueText(completion);
318
321
  const choice=completion.choices[0];
319
322
  const content=choice?.message?.content;
@@ -323,17 +326,17 @@ function textFromCompletion(completion) {
323
326
  function structuralToolCall(call,location){
324
327
  if(
325
328
  !call
326
- ||typeof call!=="object"
327
- ||Array.isArray(call)
329
+ ||!is.object(call)
330
+ ||is.array(call)
328
331
  ||call.type!=="function"
329
- ||typeof call.id!=="string"
332
+ ||!is.string(call.id)
330
333
  ||!call.id.trim()
331
334
  ||!call.function
332
- ||typeof call.function!=="object"
333
- ||Array.isArray(call.function)
334
- ||typeof call.function.name!=="string"
335
+ ||!is.object(call.function)
336
+ ||is.array(call.function)
337
+ ||!is.string(call.function.name)
335
338
  ||!call.function.name.trim()
336
- ||typeof call.function.arguments!=="string"
339
+ ||!is.string(call.function.arguments)
337
340
  ){
338
341
  throw new ArcaneAIError(
339
342
  "ARCANE_AI_TOOL_CALL_INVALID",
@@ -353,8 +356,8 @@ function structuralToolCall(call,location){
353
356
  }
354
357
  if(
355
358
  !argumentsRecord
356
- ||typeof argumentsRecord!=="object"
357
- ||Array.isArray(argumentsRecord)
359
+ ||!is.object(argumentsRecord)
360
+ ||is.array(argumentsRecord)
358
361
  ){
359
362
  throw new ArcaneAIError(
360
363
  "ARCANE_AI_TOOL_CALL_INVALID",
@@ -362,7 +365,7 @@ function structuralToolCall(call,location){
362
365
  {operation:"request"},
363
366
  );
364
367
  }
365
- if(typeof argumentsRecord.message!=="string"||!argumentsRecord.message.trim()){
368
+ if(!is.string(argumentsRecord.message)||!argumentsRecord.message.trim()){
366
369
  throw new ArcaneAIError(
367
370
  "ARCANE_AI_TOOL_MESSAGE_REQUIRED",
368
371
  `${location} arguments must include a nonempty user-facing message.`,
@@ -382,14 +385,14 @@ function structuralToolCall(call,location){
382
385
  }
383
386
 
384
387
  function plainStructuralRecord(value){
385
- if(!value||typeof value!=="object"||Array.isArray(value)) return false;
388
+ if(!value||!is.object(value)||is.array(value)) return false;
386
389
  const prototype=Object.getPrototypeOf(value);
387
390
  return prototype===Object.prototype||prototype===null;
388
391
  }
389
392
 
390
393
  function requireToolMessageSchemas(value,location){
391
394
  if(value===undefined) return;
392
- if(!Array.isArray(value)){
395
+ if(!is.array(value)){
393
396
  throw new ArcaneAIError(
394
397
  "ARCANE_AI_TOOL_CALL_INVALID",
395
398
  `${location} must be an array.`,
@@ -408,9 +411,9 @@ function requireToolMessageSchemas(value,location){
408
411
  ||!plainStructuralRecord(parameters.properties)
409
412
  ||!plainStructuralRecord(messageSchema)
410
413
  ||messageSchema.type!=="string"
411
- ||!Number.isInteger(messageSchema.minLength)
414
+ ||!is.integer(messageSchema.minLength)
412
415
  ||messageSchema.minLength<1
413
- ||!Array.isArray(parameters.required)
416
+ ||!is.array(parameters.required)
414
417
  ||!parameters.required.includes("message")
415
418
  ){
416
419
  throw new ArcaneAIError(
@@ -448,7 +451,7 @@ function structuralCallsFromMessage(message,location){
448
451
  if(
449
452
  !descriptor
450
453
  ||!Object.hasOwn(descriptor,"value")
451
- ||!Array.isArray(descriptor.value)
454
+ ||!is.array(descriptor.value)
452
455
  ){
453
456
  throw new ArcaneAIError(
454
457
  "ARCANE_AI_TOOL_CALL_INVALID",
@@ -463,12 +466,12 @@ function structuralRequest(value){
463
466
  if(!plainStructuralRecord(value)){
464
467
  throw new TypeError("AI request options must be a plain object.");
465
468
  }
466
- if(value.messages!==undefined&&!Array.isArray(value.messages)){
469
+ if(value.messages!==undefined&&!is.array(value.messages)){
467
470
  throw new TypeError("AI request messages must be an array.");
468
471
  }
469
472
  requireToolMessageSchemas(value.tools,"AI request tools");
470
473
  for(const key of ["parallelToolCalls","parallel_tool_calls"]){
471
- if(Object.hasOwn(value,key)&&typeof value[key]!=="boolean"){
474
+ if(Object.hasOwn(value,key)&&!is.boolean(value[key])){
472
475
  throw new TypeError(`AI request ${key} must be a boolean when provided.`);
473
476
  }
474
477
  }
@@ -481,7 +484,7 @@ function structuralRequest(value){
481
484
  );
482
485
  let openedToolCall=false;
483
486
  if(Object.hasOwn(message,"tool_calls")){
484
- if(message?.role!=="assistant"||!Array.isArray(calls)){
487
+ if(message?.role!=="assistant"||!is.array(calls)){
485
488
  throw new ArcaneAIError(
486
489
  "ARCANE_AI_TOOL_CALL_INVALID",
487
490
  `AI request messages[${String(messageIndex)}].tool_calls is invalid.`,
@@ -515,7 +518,7 @@ function structuralRequest(value){
515
518
  }
516
519
  if(message?.role==="tool"){
517
520
  if(
518
- typeof message.content!=="string"
521
+ !is.string(message.content)
519
522
  ||!message.content.trim()
520
523
  ){
521
524
  throw new ArcaneAIError(
@@ -526,7 +529,7 @@ function structuralRequest(value){
526
529
  }
527
530
  if(
528
531
  !pendingToolCallIds.size
529
- ||typeof message.tool_call_id!=="string"
532
+ ||!is.string(message.tool_call_id)
530
533
  ||!pendingToolCallIds.has(message.tool_call_id)
531
534
  ){
532
535
  throw new ArcaneAIError(
@@ -556,7 +559,7 @@ function structuralRequest(value){
556
559
  }
557
560
 
558
561
  function toolRecordFromCompletion(completion) {
559
- if(typeof completion==="string")return null;
562
+ if(is.string(completion))return null;
560
563
  if(!plainStructuralRecord(completion)){
561
564
  throw new ArcaneAIError(
562
565
  "ARCANE_AI_INVALID_PROVIDER_RESULT",
@@ -595,7 +598,7 @@ function toolRecordFromCompletion(completion) {
595
598
  if(
596
599
  !descriptor
597
600
  ||!Object.hasOwn(descriptor,"value")
598
- ||!Array.isArray(descriptor.value)
601
+ ||!is.array(descriptor.value)
599
602
  ||!descriptor.value.length
600
603
  ){
601
604
  throw new ArcaneAIError(
@@ -611,7 +614,7 @@ function toolRecordFromCompletion(completion) {
611
614
  :null;
612
615
  if(
613
616
  !plainStructuralRecord(choice)
614
- ||!Number.isSafeInteger(choice.index)
617
+ ||!is.safeInteger(choice.index)
615
618
  ||choice.index<0
616
619
  ||indexes.has(choice.index)
617
620
  ||!messageDescriptor
@@ -669,9 +672,9 @@ function sameCompleteStreamValue(left,right,leftToRight=new Map(),rightToLeft=ne
669
672
  if(
670
673
  !left
671
674
  ||!right
672
- ||typeof left!=="object"
673
- ||typeof right!=="object"
674
- ||Array.isArray(left)!==Array.isArray(right)
675
+ ||!is.object(left)
676
+ ||!is.object(right)
677
+ ||is.array(left)!==is.array(right)
675
678
  )return false;
676
679
  if(leftToRight.has(left)||rightToLeft.has(right)){
677
680
  return leftToRight.get(left)===right&&rightToLeft.get(right)===left;
@@ -768,13 +771,13 @@ function createStreamedToolCallAccumulator(){
768
771
  }
769
772
  if(!Object.hasOwn(delta,"tool_calls"))return;
770
773
  const descriptor=Object.getOwnPropertyDescriptor(delta,"tool_calls");
771
- if(!descriptor||!Object.hasOwn(descriptor,"value")||!Array.isArray(descriptor.value)){
774
+ if(!descriptor||!Object.hasOwn(descriptor,"value")||!is.array(descriptor.value)){
772
775
  throw mismatch(`${location}.tool_calls must be an array data property.`);
773
776
  }
774
777
  for(const [fragmentPosition,fragment] of descriptor.value.entries()){
775
778
  if(
776
779
  !plainStructuralRecord(fragment)
777
- ||!Number.isSafeInteger(fragment.index)
780
+ ||!is.safeInteger(fragment.index)
778
781
  ||fragment.index<0
779
782
  ){
780
783
  throw mismatch(
@@ -790,7 +793,7 @@ function createStreamedToolCallAccumulator(){
790
793
  };
791
794
  if(Object.hasOwn(fragment,"id")){
792
795
  if(
793
- typeof fragment.id!=="string"
796
+ !is.string(fragment.id)
794
797
  ||!fragment.id
795
798
  ||current.id&&current.id!==fragment.id
796
799
  ){
@@ -800,7 +803,7 @@ function createStreamedToolCallAccumulator(){
800
803
  }
801
804
  if(Object.hasOwn(fragment,"type")){
802
805
  if(
803
- typeof fragment.type!=="string"
806
+ !is.string(fragment.type)
804
807
  ||!fragment.type
805
808
  ||current.type&&current.type!==fragment.type
806
809
  ){
@@ -813,13 +816,13 @@ function createStreamedToolCallAccumulator(){
813
816
  throw mismatch("A streamed structural tool call has an invalid function fragment.");
814
817
  }
815
818
  if(Object.hasOwn(fragment.function,"name")){
816
- if(typeof fragment.function.name!=="string"){
819
+ if(!is.string(fragment.function.name)){
817
820
  throw mismatch("A streamed structural tool call has an invalid function-name fragment.");
818
821
  }
819
822
  current.name+=fragment.function.name;
820
823
  }
821
824
  if(Object.hasOwn(fragment.function,"arguments")){
822
- if(typeof fragment.function.arguments!=="string"){
825
+ if(!is.string(fragment.function.arguments)){
823
826
  throw mismatch("A streamed structural tool call has an invalid arguments fragment.");
824
827
  }
825
828
  current.arguments+=fragment.function.arguments;
@@ -837,13 +840,13 @@ function createStreamedToolCallAccumulator(){
837
840
  rememberDelta(state,chunk.delta,"The streamed model delta");
838
841
  rememberCompleteMessage(state,chunk.message,"The streamed model message");
839
842
  }
840
- if(!Array.isArray(chunk.choices)||!chunk.choices.length)return;
843
+ if(!is.array(chunk.choices)||!chunk.choices.length)return;
841
844
  for(const [position,choice] of chunk.choices.entries()){
842
845
  if(
843
846
  !plainStructuralRecord(choice)
844
847
  ||(!structuralRecord(choice.delta)&&!structuralRecord(choice.message))
845
848
  )continue;
846
- if(!Number.isSafeInteger(choice.index)||choice.index<0){
849
+ if(!is.safeInteger(choice.index)||choice.index<0){
847
850
  throw mismatch(
848
851
  `The streamed model choice ${String(position)} has no valid choice index.`,
849
852
  );
@@ -889,7 +892,7 @@ function createStreamedToolCallAccumulator(){
889
892
 
890
893
  function terminalChoices(completion){
891
894
  const result=new Map();
892
- if(typeof completion==="string")return result;
895
+ if(is.string(completion))return result;
893
896
  if(Object.hasOwn(completion,"message")){
894
897
  result.set(
895
898
  directChoice,
@@ -949,9 +952,9 @@ function isPublicStreamStructuralKey(key){
949
952
  const OMITTED_PUBLIC_STREAM_DATA=Symbol("omitted-public-stream-data");
950
953
 
951
954
  function projectPublicStreamData(value,seen=new Map()){
952
- if(value===null||value===undefined||typeof value!=="object")return value;
955
+ if(value===null||value===undefined||!is.object(value))return value;
953
956
  if(seen.has(value))return seen.get(value);
954
- if(Array.isArray(value)){
957
+ if(is.array(value)){
955
958
  const result=[];
956
959
  seen.set(value,result);
957
960
  for(const item of value){
@@ -998,7 +1001,7 @@ export class ModelController {
998
1001
  #error = null;
999
1002
 
1000
1003
  constructor({ provider, loadPolicy = "on-demand", security } = {}) {
1001
- if (!provider || typeof provider !== "object") {
1004
+ if (!provider || !is.object(provider)) {
1002
1005
  throw new TypeError("ModelController requires an LLM provider.");
1003
1006
  }
1004
1007
  if (
@@ -1074,9 +1077,9 @@ export class ModelController {
1074
1077
  this.#events.dispatch(type, status, {
1075
1078
  operationId,
1076
1079
  publicDetail: {
1077
- ...(typeof status.state === "string" ? { state: status.state } : {}),
1080
+ ...(is.string(status.state) ? { state: status.state } : {}),
1078
1081
  ...(progress ? { progress } : {}),
1079
- ...(typeof status.error?.code === "string" ? { code: status.error.code } : {}),
1082
+ ...(is.string(status.error?.code) ? { code: status.error.code } : {}),
1080
1083
  },
1081
1084
  });
1082
1085
  }
@@ -1364,12 +1367,12 @@ export class ModelController {
1364
1367
  );
1365
1368
  if (
1366
1369
  !value
1367
- ||typeof value[Symbol.asyncIterator]!=="function"
1368
- ||typeof value.cancel!=="function"
1370
+ ||!is.function(value[Symbol.asyncIterator])
1371
+ ||!is.function(value.cancel)
1369
1372
  ||!value.result
1370
- ||typeof value.result.then!=="function"
1373
+ ||!is.function(value.result.then)
1371
1374
  ) {
1372
- if(typeof value?.cancel==="function"){
1375
+ if(is.function(value?.cancel)){
1373
1376
  Promise.resolve().then(()=>value.cancel(
1374
1377
  "The provider returned an invalid stream handle.",
1375
1378
  )).catch(function reportInvalidModelStreamCleanupFailure(error){
@@ -1392,7 +1395,7 @@ export class ModelController {
1392
1395
  );
1393
1396
  throw error;
1394
1397
  }
1395
- if(!iterator||typeof iterator.next!=="function"){
1398
+ if(!iterator||!is.function(iterator.next)){
1396
1399
  Promise.resolve().then(()=>value.cancel(
1397
1400
  "The provider returned an invalid stream iterator.",
1398
1401
  )).catch(function reportInvalidModelIteratorCleanupFailure(error){
@@ -1520,7 +1523,7 @@ export class ModelController {
1520
1523
  throw normalizeArcaneAIError(null, { operation: "request", signal: options.signal });
1521
1524
  }
1522
1525
  toolRecordFromCompletion(response);
1523
- if (typeof options.onResponse === "function") {
1526
+ if (is.function(options.onResponse)) {
1524
1527
  await options.onResponse(response, id, false);
1525
1528
  }
1526
1529
  return response;
@@ -1539,21 +1542,21 @@ export class ModelController {
1539
1542
  for await (const chunk of handle) {
1540
1543
  if (options.signal?.aborted) break;
1541
1544
  await options.onDataChunk?.(chunk, id);
1542
- if(typeof chunk==="string"){
1545
+ if(is.string(chunk)){
1543
1546
  if(chunk)await options.onChunk?.(chunk, displayId, false);
1544
1547
  continue;
1545
1548
  }
1546
- const streamedChoices=Array.isArray(chunk?.choices)?chunk.choices:[];
1549
+ const streamedChoices=is.array(chunk?.choices)?chunk.choices:[];
1547
1550
  for(const choice of streamedChoices){
1548
1551
  const delta = choice?.delta ?? {};
1549
1552
  if (
1550
- typeof delta.reasoning_content === "string"
1553
+ is.string(delta.reasoning_content)
1551
1554
  &&delta.reasoning_content
1552
1555
  &&options.seeThinking === true
1553
1556
  ) {
1554
1557
  await options.onChunk?.(delta.reasoning_content, displayId, true);
1555
1558
  }
1556
- if (typeof delta.content === "string"&&delta.content) {
1559
+ if (is.string(delta.content)&&delta.content) {
1557
1560
  await options.onChunk?.(delta.content, displayId, false);
1558
1561
  }
1559
1562
  }
@@ -1564,17 +1567,17 @@ export class ModelController {
1564
1567
  }
1565
1568
  const tools = toolRecordFromCompletion(completion);
1566
1569
  await options.onDataResult?.(completion, id);
1567
- if (typeof options.onResponse === "function") {
1570
+ if (is.function(options.onResponse)) {
1568
1571
  await options.onResponse(completion, id, false);
1569
1572
  }
1570
1573
  for (const call of tools ?? []) {
1571
1574
  await options.onToolCall?.(call, displayId);
1572
1575
  }
1573
- const multipleChoices=Array.isArray(completion?.choices)&&completion.choices.length>1;
1576
+ const multipleChoices=is.array(completion?.choices)&&completion.choices.length>1;
1574
1577
  const output = multipleChoices
1575
1578
  ?textFromCompletion(completion)
1576
1579
  :tools??textFromCompletion(completion);
1577
- if (typeof options.onComplete === "function") {
1580
+ if (is.function(options.onComplete)) {
1578
1581
  await options.onComplete(output, displayId, false);
1579
1582
  }
1580
1583
  return output;
@@ -1,3 +1,4 @@
1
+ import Is from "../dependencies/strong-type/index.js";
1
2
  import {
2
3
  collectSpeechTransferables,
3
4
  normalizeSpeechWorkerErrorEnvelope,
@@ -5,6 +6,8 @@ import {
5
6
  } from "./speech-worker-runtime.mjs";
6
7
  import { arcaneLogging } from "../logging.mjs";
7
8
 
9
+ const is = new Is(false);
10
+
8
11
  const completeValue = (value) => value;
9
12
  let nextSpeechWorkerClientId = 0;
10
13
 
@@ -25,7 +28,7 @@ function clientError(code, message, cause, reason) {
25
28
  ? "AbortError"
26
29
  : "ArcaneSpeechWorkerError";
27
30
  error.code = code;
28
- if (typeof reason === "string" && reason) error.reason = reason;
31
+ if (is.string(reason) && reason) error.reason = reason;
29
32
  WORKER_CLIENT_ERRORS.add(error);
30
33
  return error;
31
34
  }
@@ -45,7 +48,7 @@ function abortError(signal, role, op) {
45
48
  }
46
49
 
47
50
  function validateWorker(worker) {
48
- if (!worker || typeof worker.postMessage !== "function" || typeof worker.terminate !== "function") {
51
+ if (!worker || !is.function(worker.postMessage) || !is.function(worker.terminate)) {
49
52
  throw new TypeError("The packaged speech worker did not create a Worker.");
50
53
  }
51
54
  return worker;
@@ -74,7 +77,7 @@ class SpeechWorkerClient {
74
77
  if (role !== "stt" && role !== "tts") {
75
78
  throw new TypeError('SpeechWorkerClient role must be "stt" or "tts".');
76
79
  }
77
- if (typeof onTermination !== "function") {
80
+ if (!is.function(onTermination)) {
78
81
  throw new TypeError("SpeechWorkerClient onTermination must be a function.");
79
82
  }
80
83
  const workerUrl = role === "stt"
@@ -163,7 +166,7 @@ class SpeechWorkerClient {
163
166
  ), { intentional: false }).catch(() => undefined);
164
167
  return;
165
168
  }
166
- if (!Number.isSafeInteger(message.id) || typeof message.ok !== "boolean") {
169
+ if (!is.safeInteger(message.id) || !is.boolean(message.ok)) {
167
170
  void this.terminate(clientError(
168
171
  "ARCANE_AI_WORKER_MESSAGE_ERROR",
169
172
  "The speech Worker response envelope shape was rejected.",
@@ -313,16 +316,16 @@ class SpeechWorkerClient {
313
316
 
314
317
  async terminate(reason = null, { intentional = true } = {}) {
315
318
  this.#trace("terminate.call", { reason, intentional });
316
- if (typeof intentional !== "boolean") {
319
+ if (!is.boolean(intentional)) {
317
320
  throw new TypeError("Speech Worker termination intent must be a boolean.");
318
321
  }
319
322
  if (this.#terminated) return;
320
- const terminationReason = reason instanceof Error && WORKER_CLIENT_ERRORS.has(reason)
323
+ const terminationReason = is.error(reason) && WORKER_CLIENT_ERRORS.has(reason)
321
324
  ? reason
322
325
  : clientError(
323
326
  "ARCANE_AI_OPERATION_SUPERSEDED",
324
327
  "The speech Worker was terminated.",
325
- reason instanceof Error ? reason : undefined,
328
+ is.error(reason) ? reason : undefined,
326
329
  `${this.#role}-worker-terminated`,
327
330
  );
328
331
  this.#terminated = true;
@@ -341,7 +344,7 @@ class SpeechWorkerClient {
341
344
  try {
342
345
  this.#transport = null;
343
346
  const termination = worker?.terminate();
344
- if (termination && typeof termination.then === "function") await termination;
347
+ if (termination && is.function(termination.then)) await termination;
345
348
  } finally {
346
349
  for (const pending of pendingOperations) pending.reject(terminationReason);
347
350
  this.#trace("terminated", { reason: terminationReason, intentional });
@@ -359,5 +362,5 @@ export function isSpeechWorkerClient(value) {
359
362
  }
360
363
 
361
364
  export function isSpeechWorkerClientError(value) {
362
- return value instanceof Error && WORKER_CLIENT_ERRORS.has(value);
365
+ return is.error(value) && WORKER_CLIENT_ERRORS.has(value);
363
366
  }