arcane-os 0.5.17 → 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 (183) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +9 -9
  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 +46 -43
  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 +5 -1
  17. package/docs/reference/ai/browser-speech.md +198 -5
  18. package/docs/reference/availability-and-normalization.md +40 -0
  19. package/docs/reference/inventory/package-api.json +1 -1
  20. package/docs/reference/inventory/runtime-components.json +1 -1
  21. package/docs/reference/inventory/runtime-modules.json +20 -24
  22. package/docs/reference/protocols.md +1 -1
  23. package/docs/reference/runtime-modules.md +125 -35
  24. package/docs/reference/sdk-api.md +23 -7
  25. package/examples/wasm-ai-demo/index.html +1 -0
  26. package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
  27. package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
  28. package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
  29. package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
  30. package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
  31. package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
  32. package/node_modules/strong-type/README.md +42 -24
  33. package/node_modules/strong-type/benchmark/core.js +324 -0
  34. package/node_modules/strong-type/index.js +207 -82
  35. package/node_modules/strong-type/package.json +14 -6
  36. package/package.json +3 -3
  37. package/runtime/arcane/components/app-bar.html +5 -2
  38. package/runtime/arcane/components/assistant-panel.html +5 -2
  39. package/runtime/arcane/components/chart.html +15 -12
  40. package/runtime/arcane/components/chat.html +117 -138
  41. package/runtime/arcane/components/dashboard-config.html +6 -3
  42. package/runtime/arcane/components/data-view.html +4 -1
  43. package/runtime/arcane/components/directory-picker.html +5 -2
  44. package/runtime/arcane/components/document-inspector.html +6 -3
  45. package/runtime/arcane/components/file-drop.html +5 -2
  46. package/runtime/arcane/components/file-inspector.html +9 -6
  47. package/runtime/arcane/components/file-manager.html +16 -13
  48. package/runtime/arcane/components/local-ai-status.html +5 -2
  49. package/runtime/arcane/components/markdown-document.html +14 -11
  50. package/runtime/arcane/components/markdown-editor.html +4 -1
  51. package/runtime/arcane/components/modal.html +4 -1
  52. package/runtime/arcane/components/output-panel.html +6 -3
  53. package/runtime/arcane/components/preferences-form.html +4 -1
  54. package/runtime/arcane/components/record-timeline.html +8 -5
  55. package/runtime/arcane/components/relationship-board.html +9 -6
  56. package/runtime/arcane/components/source-code-viewer.html +12 -9
  57. package/runtime/arcane/components/source-explanation.html +8 -5
  58. package/runtime/arcane/components/speech.html +24 -21
  59. package/runtime/arcane/components/summary-strip.html +4 -1
  60. package/runtime/arcane/components/task-progress.html +6 -3
  61. package/runtime/arcane/components/terminal-workspace.html +4 -1
  62. package/runtime/arcane/components/voice-transcription.html +11 -8
  63. package/runtime/arcane/components/web-navigator.html +5 -2
  64. package/runtime/arcane/entities/ApiModelRecord.js +5 -2
  65. package/runtime/arcane/entities/Calculation.js +5 -2
  66. package/runtime/arcane/entities/Chat.js +33 -33
  67. package/runtime/arcane/entities/CommunicationMessage.js +4 -1
  68. package/runtime/arcane/entities/CommunicationThread.js +4 -1
  69. package/runtime/arcane/entities/File.js +1 -1
  70. package/runtime/arcane/entities/Image.js +8 -5
  71. package/runtime/arcane/entities/IntentEnvelope.js +20 -17
  72. package/runtime/arcane/entities/Preference.js +9 -6
  73. package/runtime/arcane/entities/Theme.js +5 -2
  74. package/runtime/arcane/entities/User.js +11 -11
  75. package/runtime/arcane/entities/Weather.js +5 -2
  76. package/runtime/arcane/modules/AI.js +491 -165
  77. package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
  78. package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
  79. package/runtime/arcane/modules/AIProviderRuntime.js +88 -85
  80. package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
  81. package/runtime/arcane/modules/AIRuntimeState.js +29 -26
  82. package/runtime/arcane/modules/AnsiText.js +4 -1
  83. package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
  84. package/runtime/arcane/modules/AppDataScope.js +8 -5
  85. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
  86. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
  87. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
  88. package/runtime/arcane/modules/AsyncBoundary.js +13 -10
  89. package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
  90. package/runtime/arcane/modules/CalculatorEngine.js +5 -2
  91. package/runtime/arcane/modules/ChatRecords.js +18 -15
  92. package/runtime/arcane/modules/CommunicationAppController.js +9 -6
  93. package/runtime/arcane/modules/CommunicationHub.js +9 -6
  94. package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
  95. package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
  96. package/runtime/arcane/modules/ComponentContracts.js +56 -53
  97. package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
  98. package/runtime/arcane/modules/ConversationActionItems.js +15 -12
  99. package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
  100. package/runtime/arcane/modules/ConversationTimebox.js +28 -25
  101. package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
  102. package/runtime/arcane/modules/DBLS.js +7 -4
  103. package/runtime/arcane/modules/DBOPFS.js +7 -7
  104. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
  105. package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
  106. package/runtime/arcane/modules/DirectoryPicker.js +8 -5
  107. package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
  108. package/runtime/arcane/modules/DocumentNavigation.js +7 -4
  109. package/runtime/arcane/modules/Errors.js +28 -25
  110. package/runtime/arcane/modules/HTMLImport.js +7 -4
  111. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
  112. package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
  113. package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
  114. package/runtime/arcane/modules/MD.js +1 -1
  115. package/runtime/arcane/modules/Mail.js +46 -43
  116. package/runtime/arcane/modules/MailOutbox.mjs +48 -45
  117. package/runtime/arcane/modules/MailTransport.mjs +29 -26
  118. package/runtime/arcane/modules/MemoryRecords.js +7 -4
  119. package/runtime/arcane/modules/MessageAdvisory.js +6 -3
  120. package/runtime/arcane/modules/ModelDefinition.js +7 -4
  121. package/runtime/arcane/modules/Ollama.js +6 -3
  122. package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
  123. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
  124. package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
  125. package/runtime/arcane/modules/PreferenceStore.js +20 -17
  126. package/runtime/arcane/modules/PreparedSpeech.js +485 -0
  127. package/runtime/arcane/modules/Questionnaire.js +6 -3
  128. package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
  129. package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
  130. package/runtime/arcane/modules/RecordReviewStore.js +12 -9
  131. package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
  132. package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
  133. package/runtime/arcane/modules/ScreenCapture.js +20 -17
  134. package/runtime/arcane/modules/SpeechPlayback.js +29 -26
  135. package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
  136. package/runtime/arcane/modules/SystemAppearance.js +5 -2
  137. package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
  138. package/runtime/arcane/modules/TerminalClient.js +14 -11
  139. package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
  140. package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
  141. package/runtime/arcane/modules/ThemeManager.js +5 -2
  142. package/runtime/arcane/modules/TimeGuard.js +1 -1
  143. package/runtime/arcane/modules/ToolCallRouter.js +11 -8
  144. package/runtime/arcane/modules/WaitForComponent.js +19 -16
  145. package/runtime/arcane/modules/YouTubeMedia.js +4 -1
  146. package/runtime/strong-type/index.js +1276 -352
  147. package/runtime/strong-type/package.json +69 -45
  148. package/src/app-descriptor.mjs +17 -14
  149. package/src/application-tests.mjs +4 -1
  150. package/src/cli/main.mjs +8 -5
  151. package/src/constants.mjs +4 -1
  152. package/src/dev-server.mjs +8 -5
  153. package/src/doctor.mjs +5 -2
  154. package/src/dom-event-instrumentation.mjs +24 -20
  155. package/src/errors.mjs +7 -3
  156. package/src/event-manager.mjs +111 -108
  157. package/src/event-queue.mjs +8 -5
  158. package/src/events.mjs +12 -9
  159. package/src/import-map.mjs +35 -27
  160. package/src/installed-sdk-runtime.mjs +4 -1
  161. package/src/integrated-provider-loader.mjs +9 -6
  162. package/src/mail-credentials.mjs +16 -13
  163. package/src/mail-server.mjs +53 -50
  164. package/src/mail.mjs +18 -15
  165. package/src/native-plan.mjs +12 -9
  166. package/src/native-provider-loader.mjs +7 -4
  167. package/src/packager/core.mjs +18 -15
  168. package/src/process.mjs +6 -3
  169. package/src/release-bundle.mjs +14 -11
  170. package/src/runtime.mjs +4 -1
  171. package/src/scaffold.mjs +9 -6
  172. package/src/sdk-browser-runtime.mjs +4 -1
  173. package/src/source-server.mjs +17 -14
  174. package/src/targets/index.mjs +5 -2
  175. package/src/templates/workspace-template.mjs +13 -6
  176. package/src/testing-loader.mjs +7 -4
  177. package/src/testing.mjs +10 -7
  178. package/src/toolchain.mjs +13 -10
  179. package/src/update-check.mjs +9 -6
  180. package/src/workspace-operation-lock.mjs +14 -11
  181. package/src/workspace-runtime.mjs +4 -1
  182. package/src/workspace.mjs +17 -14
  183. package/runtime/arcane/modules/AIResponseLength.js +0 -32
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import {arcaneLogging} from 'arcane-os/logging';
2
5
  import {stripSpeechFormatting} from 'arcane-os/speech-text';
3
6
  import {
@@ -88,7 +91,7 @@ async function awaitStreamCleanup(operation) {
88
91
 
89
92
  function assertStreamCleanupComplete(outcome) {
90
93
  const rejected = outcome.completed
91
- && Array.isArray(outcome.results)
94
+ && is.array(outcome.results)
92
95
  && outcome.results.some(function hasRejectedAIStreamCleanup(result) {
93
96
  return result.status === 'rejected';
94
97
  });
@@ -107,7 +110,7 @@ function assertRole(role) {
107
110
  }
108
111
 
109
112
  function assertIdentifier(value, label) {
110
- if (typeof value !== 'string'
113
+ if (!is.string(value)
111
114
  || value.length < 1
112
115
  || value.trim() !== value) {
113
116
  fail(`${label} must be a nonempty trimmed string.`);
@@ -115,7 +118,7 @@ function assertIdentifier(value, label) {
115
118
  }
116
119
 
117
120
  function nextSequence(value) {
118
- if (typeof value === 'bigint') {
121
+ if (is.bigint(value)) {
119
122
  return value + 1n;
120
123
  }
121
124
  if (value === Number.MAX_SAFE_INTEGER) {
@@ -129,16 +132,16 @@ function assertAbortSignal(signal) {
129
132
  return;
130
133
  }
131
134
 
132
- if (typeof signal !== 'object'
133
- || typeof signal.aborted !== 'boolean'
134
- || typeof signal.addEventListener !== 'function'
135
- || typeof signal.removeEventListener !== 'function') {
135
+ if (!is.object(signal)
136
+ || !is.boolean(signal.aborted)
137
+ || !is.function(signal.addEventListener)
138
+ || !is.function(signal.removeEventListener)) {
136
139
  fail('AI provider operation signal must be an AbortSignal.');
137
140
  }
138
141
  }
139
142
 
140
143
  function assertPlainObject(value, label) {
141
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
144
+ if (!value || !is.object(value) || is.array(value)) {
142
145
  fail(`${label} must be a plain object.`);
143
146
  }
144
147
 
@@ -149,7 +152,7 @@ function assertPlainObject(value, label) {
149
152
  }
150
153
 
151
154
  function isPlainRecord(value) {
152
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
155
+ if (!value || !is.object(value) || is.array(value)) {
153
156
  return false;
154
157
  }
155
158
  const prototype = Object.getPrototypeOf(value);
@@ -160,7 +163,7 @@ function validateLLMToolDeclarations(value) {
160
163
  if (value === undefined) {
161
164
  return;
162
165
  }
163
- if (!Array.isArray(value)) {
166
+ if (!is.array(value)) {
164
167
  fail(
165
168
  'AI LLM tools must be an array.',
166
169
  'ARCANE_AI_TOOL_CALL_INVALID'
@@ -178,9 +181,9 @@ function validateLLMToolDeclarations(value) {
178
181
  || !isPlainRecord(parameters.properties)
179
182
  || !isPlainRecord(messageSchema)
180
183
  || messageSchema.type !== 'string'
181
- || !Number.isInteger(messageSchema.minLength)
184
+ || !is.integer(messageSchema.minLength)
182
185
  || messageSchema.minLength < 1
183
- || !Array.isArray(parameters.required)
186
+ || !is.array(parameters.required)
184
187
  || !parameters.required.includes('message')) {
185
188
  fail(
186
189
  `AI LLM tools[${index}] must require a nonempty string parameters.properties.message.`,
@@ -192,13 +195,13 @@ function validateLLMToolDeclarations(value) {
192
195
 
193
196
  function validateLLMToolCall(call, label) {
194
197
  if (!isPlainRecord(call)
195
- || typeof call.id !== 'string'
198
+ || !is.string(call.id)
196
199
  || !call.id.trim()
197
200
  || call.type !== 'function'
198
201
  || !isPlainRecord(call.function)
199
- || typeof call.function.name !== 'string'
202
+ || !is.string(call.function.name)
200
203
  || !call.function.name.trim()
201
- || typeof call.function.arguments !== 'string') {
204
+ || !is.string(call.function.arguments)) {
202
205
  fail(
203
206
  `${label} must be one complete structural function call.`,
204
207
  'ARCANE_AI_TOOL_CALL_INVALID'
@@ -220,7 +223,7 @@ function validateLLMToolCall(call, label) {
220
223
  'ARCANE_AI_TOOL_CALL_INVALID'
221
224
  );
222
225
  }
223
- if (typeof argumentsRecord.message !== 'string'
226
+ if (!is.string(argumentsRecord.message)
224
227
  || !argumentsRecord.message.trim()) {
225
228
  fail(
226
229
  `${label} arguments must include a nonempty user-facing message.`,
@@ -252,7 +255,7 @@ function validateLLMMessageToolCalls(message, label) {
252
255
  }
253
256
  const descriptor = Object.getOwnPropertyDescriptor(message, 'tool_calls');
254
257
  if (!descriptor || !Object.hasOwn(descriptor, 'value')
255
- || !Array.isArray(descriptor.value)) {
258
+ || !is.array(descriptor.value)) {
256
259
  fail(
257
260
  `${label}.tool_calls must be an array data property.`,
258
261
  'ARCANE_AI_TOOL_CALL_INVALID'
@@ -277,7 +280,7 @@ function validateLLMMessageToolCalls(message, label) {
277
280
 
278
281
  function validateLLMRequestPayload(payload) {
279
282
  assertPlainObject(payload, 'AI LLM request payload');
280
- if (!Array.isArray(payload.messages)) {
283
+ if (!is.array(payload.messages)) {
281
284
  fail('AI LLM request payload.messages must be an array.');
282
285
  }
283
286
  const pendingToolCallIds = new Set();
@@ -317,7 +320,7 @@ function validateLLMRequestPayload(payload) {
317
320
  openedToolCall = true;
318
321
  }
319
322
  if (message.role === 'tool') {
320
- if (typeof message.content !== 'string'
323
+ if (!is.string(message.content)
321
324
  || !message.content.trim()) {
322
325
  fail(
323
326
  `AI LLM request payload.messages[${index}] must contain a nonblank user-facing tool result.`,
@@ -325,7 +328,7 @@ function validateLLMRequestPayload(payload) {
325
328
  );
326
329
  }
327
330
  if (!pendingToolCallIds.size
328
- || typeof message.tool_call_id !== 'string'
331
+ || !is.string(message.tool_call_id)
329
332
  || !pendingToolCallIds.has(message.tool_call_id)) {
330
333
  fail(
331
334
  `AI LLM request payload.messages[${index}] does not settle the pending structural tool call.`,
@@ -360,7 +363,7 @@ function validateLLMRequestPayload(payload) {
360
363
  payload.parallel_tool_calls
361
364
  ];
362
365
  if (parallelValues.some(function invalidParallelLLMToolPreference(value) {
363
- return value !== undefined && typeof value !== 'boolean';
366
+ return value !== undefined && !is.boolean(value);
364
367
  })) {
365
368
  fail('AI LLM parallel tool-call preferences must be boolean when provided.');
366
369
  }
@@ -377,7 +380,7 @@ function validateLLMTerminalMessage(message, label) {
377
380
  }
378
381
 
379
382
  function validateLLMTerminalResult(value, label) {
380
- if (typeof value === 'string') {
383
+ if (is.string(value)) {
381
384
  return value;
382
385
  }
383
386
  if (!isPlainRecord(value)) {
@@ -407,7 +410,7 @@ function validateLLMTerminalResult(value, label) {
407
410
  }
408
411
  const choicesDescriptor = Object.getOwnPropertyDescriptor(value, 'choices');
409
412
  if (!choicesDescriptor || !Object.hasOwn(choicesDescriptor, 'value')
410
- || !Array.isArray(choicesDescriptor.value)
413
+ || !is.array(choicesDescriptor.value)
411
414
  || choicesDescriptor.value.length === 0) {
412
415
  fail(
413
416
  `${label}.choices must be a nonempty array data property.`,
@@ -418,7 +421,7 @@ function validateLLMTerminalResult(value, label) {
418
421
  for (let position = 0; position < choicesDescriptor.value.length; position += 1) {
419
422
  const choice = choicesDescriptor.value[position];
420
423
  if (!isPlainRecord(choice)
421
- || !Number.isSafeInteger(choice.index)
424
+ || !is.safeInteger(choice.index)
422
425
  || choice.index < 0
423
426
  || indexes.has(choice.index)) {
424
427
  fail(
@@ -462,22 +465,22 @@ function canonicalLLMToolCall(call) {
462
465
  }
463
466
 
464
467
  function copyLLMDataValue(value, seen = new Map()) {
465
- if (!value || typeof value !== 'object') {
468
+ if (!value || !is.object(value)) {
466
469
  return value;
467
470
  }
468
471
  if (seen.has(value)) {
469
472
  return seen.get(value);
470
473
  }
471
- if (!Array.isArray(value) && !isPlainRecord(value)) {
474
+ if (!is.array(value) && !isPlainRecord(value)) {
472
475
  return value;
473
476
  }
474
- const result = Array.isArray(value)
477
+ const result = is.array(value)
475
478
  ? new Array(value.length)
476
479
  : Object.create(Object.getPrototypeOf(value));
477
480
  seen.set(value, result);
478
481
  const descriptors = Object.getOwnPropertyDescriptors(value);
479
482
  for (const key of Reflect.ownKeys(descriptors)) {
480
- if (Array.isArray(value) && key === 'length') {
483
+ if (is.array(value) && key === 'length') {
481
484
  continue;
482
485
  }
483
486
  const descriptor = descriptors[key];
@@ -494,12 +497,12 @@ function sameLLMDataValue(left, right, seen = new Map()) {
494
497
  return true;
495
498
  }
496
499
  if (!left || !right
497
- || typeof left !== 'object'
498
- || typeof right !== 'object'
499
- || Array.isArray(left) !== Array.isArray(right)) {
500
+ || !is.object(left)
501
+ || !is.object(right)
502
+ || is.array(left) !== is.array(right)) {
500
503
  return false;
501
504
  }
502
- if (!Array.isArray(left)
505
+ if (!is.array(left)
503
506
  && (!isPlainRecord(left) || !isPlainRecord(right))) {
504
507
  return false;
505
508
  }
@@ -562,11 +565,11 @@ function terminalLLMToolCallRecord(choiceIndex, completeCalls) {
562
565
  }
563
566
 
564
567
  function terminalLLMToolCalls(value) {
565
- if (typeof value === 'string') {
568
+ if (is.string(value)) {
566
569
  return {direct: terminalLLMToolCallRecord(null, []), choices: []};
567
570
  }
568
571
  if (Object.hasOwn(value, 'message')) {
569
- const completeCalls = Array.isArray(value.message?.tool_calls)
572
+ const completeCalls = is.array(value.message?.tool_calls)
570
573
  ? value.message.tool_calls
571
574
  : [];
572
575
  return {
@@ -577,7 +580,7 @@ function terminalLLMToolCalls(value) {
577
580
  return {
578
581
  direct: null,
579
582
  choices: value.choices.map(function retainTerminalLLMChoice(choice) {
580
- const completeCalls = Array.isArray(choice.message?.tool_calls)
583
+ const completeCalls = is.array(choice.message?.tool_calls)
581
584
  ? choice.message.tool_calls
582
585
  : [];
583
586
  return terminalLLMToolCallRecord(choice.index, completeCalls);
@@ -637,7 +640,7 @@ function createLLMStreamToolCallCorrelation() {
637
640
  }
638
641
  const descriptor = Object.getOwnPropertyDescriptor(source, 'tool_calls');
639
642
  if (!descriptor || !Object.hasOwn(descriptor, 'value')
640
- || !Array.isArray(descriptor.value)) {
643
+ || !is.array(descriptor.value)) {
641
644
  throw llmStreamToolCallMismatch(
642
645
  `${label}.tool_calls must be an array data property.`
643
646
  );
@@ -686,7 +689,7 @@ function createLLMStreamToolCallCorrelation() {
686
689
  );
687
690
  }
688
691
  if (fragment.index !== undefined
689
- && (!Number.isSafeInteger(fragment.index) || fragment.index < 0)) {
692
+ && (!is.safeInteger(fragment.index) || fragment.index < 0)) {
690
693
  throw llmStreamToolCallMismatch(
691
694
  `${label}.tool_calls[${position}] has an invalid structural call index.`
692
695
  );
@@ -701,7 +704,7 @@ function createLLMStreamToolCallCorrelation() {
701
704
  invalid: false
702
705
  };
703
706
  if (fragment.id !== undefined) {
704
- if (typeof fragment.id !== 'string'
707
+ if (!is.string(fragment.id)
705
708
  || !fragment.id
706
709
  || (retained.id && retained.id !== fragment.id)) {
707
710
  retained.invalid = true;
@@ -710,7 +713,7 @@ function createLLMStreamToolCallCorrelation() {
710
713
  }
711
714
  }
712
715
  if (fragment.type !== undefined) {
713
- if (typeof fragment.type !== 'string'
716
+ if (!is.string(fragment.type)
714
717
  || !fragment.type
715
718
  || (retained.type && retained.type !== fragment.type)) {
716
719
  retained.invalid = true;
@@ -726,14 +729,14 @@ function createLLMStreamToolCallCorrelation() {
726
729
  ? fragment.function
727
730
  : {};
728
731
  if (functionFragment.name !== undefined) {
729
- if (typeof functionFragment.name !== 'string') {
732
+ if (!is.string(functionFragment.name)) {
730
733
  retained.invalid = true;
731
734
  } else {
732
735
  retained.name += functionFragment.name;
733
736
  }
734
737
  }
735
738
  if (functionFragment.arguments !== undefined) {
736
- if (typeof functionFragment.arguments !== 'string') {
739
+ if (!is.string(functionFragment.arguments)) {
737
740
  retained.invalid = true;
738
741
  } else {
739
742
  retained.arguments += functionFragment.arguments;
@@ -762,7 +765,7 @@ function createLLMStreamToolCallCorrelation() {
762
765
  }
763
766
  );
764
767
  if (choice.index !== undefined
765
- && (!Number.isSafeInteger(choice.index) || choice.index < 0)) {
768
+ && (!is.safeInteger(choice.index) || choice.index < 0)) {
766
769
  throw llmStreamToolCallMismatch(
767
770
  `AI provider stream choice ${position} has an invalid index.`
768
771
  );
@@ -811,7 +814,7 @@ function createLLMStreamToolCallCorrelation() {
811
814
  'AI provider stream chunk.message'
812
815
  );
813
816
  }
814
- if (Array.isArray(chunk.choices)) {
817
+ if (is.array(chunk.choices)) {
815
818
  for (let position = 0; position < chunk.choices.length; position += 1) {
816
819
  observeChoice(chunk.choices[position], position);
817
820
  }
@@ -1009,13 +1012,13 @@ function projectLLMStreamData(value, seen = new Map()) {
1009
1012
  if (value === null || value === undefined) {
1010
1013
  return value;
1011
1014
  }
1012
- if (typeof value !== 'object') {
1015
+ if (!is.object(value)) {
1013
1016
  return value;
1014
1017
  }
1015
1018
  if (seen.has(value)) {
1016
1019
  return seen.get(value);
1017
1020
  }
1018
- if (Array.isArray(value)) {
1021
+ if (is.array(value)) {
1019
1022
  const result = [];
1020
1023
  seen.set(value, result);
1021
1024
  for (const item of value) {
@@ -1031,7 +1034,7 @@ function projectLLMStreamData(value, seen = new Map()) {
1031
1034
  let sourceDataFields = 0;
1032
1035
  const descriptors = Object.getOwnPropertyDescriptors(value);
1033
1036
  for (const key of Reflect.ownKeys(descriptors)) {
1034
- if (typeof key === 'symbol') {
1037
+ if (is.symbol(key)) {
1035
1038
  continue;
1036
1039
  }
1037
1040
  const descriptor = descriptors[key];
@@ -1055,19 +1058,19 @@ function projectLLMStreamChunk(value) {
1055
1058
  }
1056
1059
 
1057
1060
  function assertCallbackFreeProviderValue(value, seen = new WeakSet()) {
1058
- if (typeof value === 'function') {
1061
+ if (is.function(value)) {
1059
1062
  fail(
1060
1063
  'AI providers receive data-only request payloads.',
1061
1064
  'ARCANE_AI_PROVIDER_CALLBACK_BOUNDARY'
1062
1065
  );
1063
1066
  }
1064
- if (!value || typeof value !== 'object') {
1067
+ if (!value || !is.object(value)) {
1065
1068
  return;
1066
1069
  }
1067
- if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
1070
+ if (value instanceof ArrayBuffer || is.arrayBufferView(value)) {
1068
1071
  return;
1069
1072
  }
1070
- if (typeof Blob === 'function' && value instanceof Blob) {
1073
+ if (is.function(globalThis.Blob) && value instanceof Blob) {
1071
1074
  return;
1072
1075
  }
1073
1076
  if (seen.has(value)) {
@@ -1077,7 +1080,7 @@ function assertCallbackFreeProviderValue(value, seen = new WeakSet()) {
1077
1080
  const descriptors = Object.getOwnPropertyDescriptors(value);
1078
1081
  for (const key of Reflect.ownKeys(descriptors)) {
1079
1082
  const descriptor = descriptors[key];
1080
- if (typeof key === 'symbol' || !Object.hasOwn(descriptor, 'value')) {
1083
+ if (is.symbol(key) || !Object.hasOwn(descriptor, 'value')) {
1081
1084
  fail('AI provider request payloads must contain data properties only.');
1082
1085
  }
1083
1086
  assertCallbackFreeProviderValue(descriptor.value, seen);
@@ -1090,7 +1093,7 @@ function assertClosedRecord(value, keys, label) {
1090
1093
  const descriptors = Object.getOwnPropertyDescriptors(value);
1091
1094
  const actual = Reflect.ownKeys(value);
1092
1095
  if (actual.some(function hasSymbolKey(key) {
1093
- return typeof key === 'symbol';
1096
+ return is.symbol(key);
1094
1097
  })) {
1095
1098
  fail(`${label} must not contain symbol keys.`);
1096
1099
  }
@@ -1119,7 +1122,7 @@ function immutableSelection(role, value) {
1119
1122
  );
1120
1123
  assertIdentifier(value.providerId, `${role} provider selection.providerId`);
1121
1124
  assertIdentifier(value.modelId, `${role} provider selection.modelId`);
1122
- if (value.localOnly !== null && typeof value.localOnly !== 'boolean') {
1125
+ if (value.localOnly !== null && !is.boolean(value.localOnly)) {
1123
1126
  fail(`${role} provider selection.localOnly must be null or a boolean.`);
1124
1127
  }
1125
1128
 
@@ -1189,7 +1192,7 @@ function immutableStartupOptions(options) {
1189
1192
  assertPlainObject(options, 'AI provider startup options');
1190
1193
  const descriptors = Object.getOwnPropertyDescriptors(options);
1191
1194
  for (const key of Reflect.ownKeys(descriptors)) {
1192
- if (typeof key === 'symbol'
1195
+ if (is.symbol(key)
1193
1196
  || (key !== 'startLanguageModel'
1194
1197
  && key !== 'startMuted'
1195
1198
  && key !== 'startTranscription'
@@ -1215,13 +1218,13 @@ function immutableStartupOptions(options) {
1215
1218
  const signal = Object.hasOwn(descriptors, 'signal')
1216
1219
  ? descriptors.signal.value
1217
1220
  : null;
1218
- if (typeof startLanguageModel !== 'boolean') {
1221
+ if (!is.boolean(startLanguageModel)) {
1219
1222
  fail('AI startup startLanguageModel must be a boolean.');
1220
1223
  }
1221
- if (typeof startMuted !== 'boolean') {
1224
+ if (!is.boolean(startMuted)) {
1222
1225
  fail('AI startup startMuted must be a boolean.');
1223
1226
  }
1224
- if (typeof startTranscription !== 'boolean') {
1227
+ if (!is.boolean(startTranscription)) {
1225
1228
  fail('AI startup startTranscription must be a boolean.');
1226
1229
  }
1227
1230
  assertAbortSignal(signal);
@@ -1237,7 +1240,7 @@ function immutableInspectionOptions(options) {
1237
1240
  assertPlainObject(options, 'AI provider inspection options');
1238
1241
  const descriptors = Object.getOwnPropertyDescriptors(options);
1239
1242
  for (const key of Reflect.ownKeys(descriptors)) {
1240
- if (typeof key === 'symbol'
1243
+ if (is.symbol(key)
1241
1244
  || (key !== 'localOnly' && key !== 'signal')) {
1242
1245
  fail('AI provider inspection options contain an unknown option.');
1243
1246
  }
@@ -1251,7 +1254,7 @@ function immutableInspectionOptions(options) {
1251
1254
  const signal = Object.hasOwn(descriptors, 'signal')
1252
1255
  ? descriptors.signal.value
1253
1256
  : null;
1254
- if (typeof localOnly !== 'boolean') {
1257
+ if (!is.boolean(localOnly)) {
1255
1258
  fail('AI provider inspection localOnly must be a boolean.');
1256
1259
  }
1257
1260
  assertAbortSignal(signal);
@@ -1259,7 +1262,7 @@ function immutableInspectionOptions(options) {
1259
1262
  }
1260
1263
 
1261
1264
  function nullableTupleIdentifier(value) {
1262
- return typeof value === 'string' && value.trim()
1265
+ return is.string(value) && value.trim()
1263
1266
  ? value
1264
1267
  : null;
1265
1268
  }
@@ -1272,7 +1275,7 @@ function completeProgress(value) {
1272
1275
  const descriptors = Object.getOwnPropertyDescriptors(value);
1273
1276
  for (const key of Reflect.ownKeys(descriptors)) {
1274
1277
  const descriptor = descriptors[key];
1275
- if (typeof key === 'symbol' || !Object.hasOwn(descriptor, 'value')) {
1278
+ if (is.symbol(key) || !Object.hasOwn(descriptor, 'value')) {
1276
1279
  fail('AI provider progress must contain string-keyed data properties only.');
1277
1280
  }
1278
1281
  result[key] = descriptor.value;
@@ -1281,7 +1284,7 @@ function completeProgress(value) {
1281
1284
  }
1282
1285
 
1283
1286
  function stateError(error, fallbackCode) {
1284
- const code = typeof error?.code === 'string'
1287
+ const code = is.string(error?.code)
1285
1288
  && /^[A-Z][A-Z0-9_]*$/.test(error.code)
1286
1289
  ? error.code
1287
1290
  : fallbackCode;
@@ -1293,7 +1296,7 @@ function stateError(error, fallbackCode) {
1293
1296
  : code.includes('UNAVAILABLE') || code.includes('NOT_REGISTERED')
1294
1297
  ? 'The selected AI provider is unavailable.'
1295
1298
  : 'The selected AI provider operation failed.';
1296
- const message = typeof error?.message === 'string' && error.message
1299
+ const message = is.string(error?.message) && error.message
1297
1300
  ? error.message
1298
1301
  : fallbackMessage;
1299
1302
  return completeValue(
@@ -1335,13 +1338,13 @@ function validateProvider(provider) {
1335
1338
  }
1336
1339
  assertRole(provider.role);
1337
1340
  assertIdentifier(provider.id, 'AI provider.id');
1338
- if (typeof provider.localOnly !== 'boolean') {
1341
+ if (!is.boolean(provider.localOnly)) {
1339
1342
  fail('AI provider.localOnly must be a boolean.');
1340
1343
  }
1341
1344
  const maxConcurrentRequests = provider.maxConcurrentRequests === undefined
1342
1345
  ? 1
1343
1346
  : provider.maxConcurrentRequests;
1344
- if (!Number.isSafeInteger(maxConcurrentRequests)
1347
+ if (!is.safeInteger(maxConcurrentRequests)
1345
1348
  || maxConcurrentRequests < 1) {
1346
1349
  fail('AI provider.maxConcurrentRequests must be a positive safe integer.');
1347
1350
  }
@@ -1349,7 +1352,7 @@ function validateProvider(provider) {
1349
1352
  fail('Only TTS providers may process more than one concurrent request.');
1350
1353
  }
1351
1354
  for (const method of PROVIDER_METHODS) {
1352
- if (typeof provider[method] !== 'function') {
1355
+ if (!is.function(provider[method])) {
1353
1356
  fail(`AI provider.${method} must be a function.`);
1354
1357
  }
1355
1358
  }
@@ -1616,9 +1619,9 @@ function immutableSpeechProviderRoleReplacement(role, value) {
1616
1619
 
1617
1620
  function validateProviderStatus(status) {
1618
1621
  assertPlainObject(status, 'AI provider status');
1619
- if (typeof status.state !== 'string'
1620
- || typeof status.loaded !== 'boolean'
1621
- || typeof status.busy !== 'boolean') {
1622
+ if (!is.string(status.state)
1623
+ || !is.boolean(status.loaded)
1624
+ || !is.boolean(status.busy)) {
1622
1625
  fail('AI provider status must include state, loaded, and busy values.');
1623
1626
  }
1624
1627
  return status;
@@ -1626,14 +1629,14 @@ function validateProviderStatus(status) {
1626
1629
 
1627
1630
  function validateInspection(inspection, selection) {
1628
1631
  assertPlainObject(inspection, 'AI provider inspection');
1629
- if (typeof inspection.available !== 'boolean') {
1632
+ if (!is.boolean(inspection.available)) {
1630
1633
  fail('AI provider inspection.available must be a boolean.');
1631
1634
  }
1632
1635
  if (!inspection.available) {
1633
- const code = typeof inspection.code === 'string' && inspection.code.length > 0
1636
+ const code = is.string(inspection.code) && inspection.code.length > 0
1634
1637
  ? inspection.code
1635
1638
  : 'ARCANE_AI_PROVIDER_AUTHORITY_BLOCKED';
1636
- const message = typeof inspection.message === 'string' && inspection.message.length > 0
1639
+ const message = is.string(inspection.message) && inspection.message.length > 0
1637
1640
  ? inspection.message
1638
1641
  : 'The selected AI provider is unavailable.';
1639
1642
  throw operationError(message, code);
@@ -1923,7 +1926,7 @@ export class AIProviderRuntime {
1923
1926
  const localOnly = Object.hasOwn(options, 'localOnly')
1924
1927
  ? options.localOnly
1925
1928
  : false;
1926
- if (typeof localOnly !== 'boolean') {
1929
+ if (!is.boolean(localOnly)) {
1927
1930
  fail('AI provider route localOnly must be a boolean.');
1928
1931
  }
1929
1932
  const slot = this.#slots[role];
@@ -2438,7 +2441,7 @@ export class AIProviderRuntime {
2438
2441
 
2439
2442
  configureFromTuple(tuple) {
2440
2443
  arcaneLogging.debug('[Arcane speech runtime] configureFromTuple', tuple);
2441
- if (!Array.isArray(tuple) || tuple.length !== 6) {
2444
+ if (!is.array(tuple) || tuple.length !== 6) {
2442
2445
  fail('AI preference tuple must contain exactly six entries.');
2443
2446
  }
2444
2447
 
@@ -2544,7 +2547,7 @@ export class AIProviderRuntime {
2544
2547
  if (SPEECH_ROLES.includes(role)) {
2545
2548
  arcaneLogging.debug('[Arcane speech runtime] provider.catalog.result', role, provider.id, providerCatalog);
2546
2549
  }
2547
- if (!Array.isArray(providerCatalog)) {
2550
+ if (!is.array(providerCatalog)) {
2548
2551
  fail('AI provider.catalog() must synchronously return an array.');
2549
2552
  }
2550
2553
  entries.push(
@@ -2728,7 +2731,7 @@ export class AIProviderRuntime {
2728
2731
  ? options.localOnly
2729
2732
  : false;
2730
2733
  assertAbortSignal(signal);
2731
- if (typeof localOnly !== 'boolean') {
2734
+ if (!is.boolean(localOnly)) {
2732
2735
  fail('AI provider load localOnly must be a boolean.');
2733
2736
  }
2734
2737
  const slot = this.#slots[role];
@@ -3384,7 +3387,7 @@ export class AIProviderRuntime {
3384
3387
  const payload = options.payload;
3385
3388
  const field = Object.hasOwn(payload ?? {}, 'input') ? 'input' : 'text';
3386
3389
  const text = Object.getOwnPropertyDescriptor(payload ?? {}, field)?.value;
3387
- if (typeof text === 'string') {
3390
+ if (is.string(text)) {
3388
3391
  const input = stripSpeechFormatting(text);
3389
3392
  if (input !== text) {
3390
3393
  const payloadProperties = Object.getOwnPropertyDescriptors(payload);
@@ -3659,14 +3662,14 @@ export class AIProviderRuntime {
3659
3662
 
3660
3663
  function beginAIProviderStreamHandleCleanup(opened, activeIterator, reason) {
3661
3664
  const cleanup = [];
3662
- if (typeof opened?.cancel === 'function') {
3665
+ if (is.function(opened?.cancel)) {
3663
3666
  cleanup.push(
3664
3667
  Promise.resolve().then(function cancelOpenedAIStream() {
3665
3668
  return opened.cancel(reason);
3666
3669
  })
3667
3670
  );
3668
3671
  }
3669
- if (activeIterator && typeof activeIterator.return === 'function') {
3672
+ if (activeIterator && is.function(activeIterator.return)) {
3670
3673
  cleanup.push(
3671
3674
  Promise.resolve().then(function returnOpenedAIStream() {
3672
3675
  return activeIterator.return();
@@ -3699,7 +3702,7 @@ export class AIProviderRuntime {
3699
3702
  }
3700
3703
  const lateCleanup = providerOpenPromise.then(
3701
3704
  async function cleanupLateAIProviderStream(lateHandle) {
3702
- if (!lateHandle || typeof lateHandle.cancel !== 'function') {
3705
+ if (!lateHandle || !is.function(lateHandle.cancel)) {
3703
3706
  throw operationError(
3704
3707
  'The late AI provider stream did not expose cancellable ownership.',
3705
3708
  'ARCANE_AI_STREAM_CLEANUP_INCOMPLETE'
@@ -3815,10 +3818,10 @@ export class AIProviderRuntime {
3815
3818
  });
3816
3819
  opened = await Promise.race([providerOpenPromise, abortedOpen]);
3817
3820
  if (!opened
3818
- || typeof opened[Symbol.asyncIterator] !== 'function'
3819
- || typeof opened.cancel !== 'function'
3821
+ || !is.function(opened[Symbol.asyncIterator])
3822
+ || !is.function(opened.cancel)
3820
3823
  || !opened.result
3821
- || typeof opened.result.then !== 'function') {
3824
+ || !is.function(opened.result.then)) {
3822
3825
  throw operationError(
3823
3826
  'AI stream providers must return an async iterable with result and cancel().',
3824
3827
  'ARCANE_AI_PROVIDER_STREAM_INVALID'
@@ -3826,7 +3829,7 @@ export class AIProviderRuntime {
3826
3829
  }
3827
3830
  providerHandle = opened;
3828
3831
  iterator = opened[Symbol.asyncIterator]();
3829
- if (!iterator || typeof iterator.next !== 'function') {
3832
+ if (!iterator || !is.function(iterator.next)) {
3830
3833
  throw operationError(
3831
3834
  'The AI provider stream iterator has no next() method.',
3832
3835
  'ARCANE_AI_PROVIDER_STREAM_INVALID'
@@ -4121,7 +4124,7 @@ export class AIProviderRuntime {
4121
4124
  arcaneLogging.debug('[Arcane speech runtime] setSpeechMuted', muted);
4122
4125
  this.#assertOpen();
4123
4126
  this.#assertNotConfiguring();
4124
- if (typeof muted !== 'boolean') {
4127
+ if (!is.boolean(muted)) {
4125
4128
  fail('AI speech muted state must be a boolean.');
4126
4129
  }
4127
4130
  this.#speechDesiredMuted = muted;