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,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  const PROVIDER_ID=/^[a-z0-9][a-z0-9._-]{1,63}$/;
2
5
 
3
6
  export default class CommunicationProviderRegistry{
@@ -5,7 +8,7 @@ export default class CommunicationProviderRegistry{
5
8
  register(provider){
6
9
  const id=String(provider?.id||'').trim().toLowerCase();
7
10
  if(!PROVIDER_ID.test(id)) throw new TypeError('Communication provider id is invalid.');
8
- for(const method of ['listThreads','getMessages','send']) if(typeof provider[method]!=='function') throw new TypeError(`Provider ${id} must implement ${method}().`);
11
+ for(const method of ['listThreads','getMessages','send']) if(!is.function(provider[method])) throw new TypeError(`Provider ${id} must implement ${method}().`);
9
12
  if(this.providers.has(id)) throw new RangeError(`Communication provider already registered: ${id}`);
10
13
  this.providers.set(id,{...provider,id,label:String(provider.label||id),channels:Object.freeze(Array.from(provider.channels||['other'])),listThreads:provider.listThreads.bind(provider),getMessages:provider.getMessages.bind(provider),send:provider.send.bind(provider)});
11
14
  return this.get(id);
@@ -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 {
3
6
  createArcaneEventSource,
@@ -46,7 +49,7 @@ function projectSTTActivationEvent(
46
49
  EventClass,
47
50
  options={}
48
51
  ){
49
- if(typeof globalThis.CustomEvent!=='function'){
52
+ if(!is.function(globalThis.CustomEvent)){
50
53
  throw codedError(
51
54
  'The STT activation DOM compatibility projection requires CustomEvent.',
52
55
  STT_ACTIVATION_ERROR_CODES.domProjectionUnavailable
@@ -81,14 +84,14 @@ function record(value,label){
81
84
  if(value===undefined){
82
85
  return {};
83
86
  }
84
- if(!value||typeof value!=='object'||Array.isArray(value)){
87
+ if(!value||!is.object(value)||is.array(value)){
85
88
  throw new TypeError(`${label} must be an object`);
86
89
  }
87
90
  return value;
88
91
  }
89
92
 
90
93
  function string(value,fallback='',allowEmpty=false){
91
- if(typeof value!=='string'){
94
+ if(!is.string(value)){
92
95
  return fallback;
93
96
  }
94
97
  const normalized=value.trim();
@@ -96,7 +99,7 @@ function string(value,fallback='',allowEmpty=false){
96
99
  }
97
100
 
98
101
  function text(value,fallback='',allowEmpty=false){
99
- if(typeof value!=='string'){
102
+ if(!is.string(value)){
100
103
  return fallback;
101
104
  }
102
105
  return value||(allowEmpty?'':fallback);
@@ -126,10 +129,10 @@ function optionAlias(input,previous,keys,fallback){
126
129
 
127
130
  function optionalCallback(input,previous,keys,label=keys[0]){
128
131
  const value=optionAlias(input,previous,keys,null);
129
- if(value!==null&&value!==undefined&&typeof value!=='function'){
132
+ if(value!==null&&value!==undefined&&!is.function(value)){
130
133
  throw new TypeError(`${label} must be a function or null`);
131
134
  }
132
- return typeof value==='function'?value:null;
135
+ return is.function(value)?value:null;
133
136
  }
134
137
 
135
138
  function finiteNumber(value,label){
@@ -137,7 +140,7 @@ function finiteNumber(value,label){
137
140
  return null;
138
141
  }
139
142
  const normalized=Number(value);
140
- if(!Number.isFinite(normalized)){
143
+ if(!is.finite(normalized)){
141
144
  throw new TypeError(`${label} must be a finite number or null`);
142
145
  }
143
146
  return normalized;
@@ -147,7 +150,7 @@ function stringRecord(value,defaults,label){
147
150
  value=record(value,label);
148
151
  const normalized={...defaults};
149
152
  for(const [key,item] of Object.entries(value)){
150
- if(typeof item!=='string'){
153
+ if(!is.string(item)){
151
154
  throw new TypeError(`${label}.${key} must be a string`);
152
155
  }
153
156
  normalized[key]=item;
@@ -189,7 +192,7 @@ function normalizeChartOptions(input={},previous={}){
189
192
  };
190
193
 
191
194
  for(const [source,target] of Object.entries(aliases)){
192
- if(typeof input[source]==='string'){
195
+ if(is.string(input[source])){
193
196
  labels[target]=input[source];
194
197
  }
195
198
  }
@@ -208,8 +211,8 @@ function normalizeChartOptions(input={},previous={}){
208
211
  const chartOptions=option(input,previous,'chartOptions',null);
209
212
  if(
210
213
  chartOptions!==null
211
- &&typeof chartOptions!=='function'
212
- &&(!chartOptions||typeof chartOptions!=='object'||Array.isArray(chartOptions))
214
+ &&!is.function(chartOptions)
215
+ &&(!chartOptions||!is.object(chartOptions)||is.array(chartOptions))
213
216
  ){
214
217
  throw new TypeError('chartOptions must be an object, function, or null');
215
218
  }
@@ -237,7 +240,7 @@ function normalizeChartOptions(input={},previous={}){
237
240
  parseY:optionalCallback(input,previous,['parseY']),
238
241
  formatTime:optionalCallback(input,previous,['formatTime']),
239
242
  formatValue:optionalCallback(input,previous,['formatValue']),
240
- chartOptions:typeof chartOptions==='object'&&chartOptions!==null
243
+ chartOptions:is.object(chartOptions)&&chartOptions!==null
241
244
  ?{...chartOptions}
242
245
  :chartOptions,
243
246
  data
@@ -249,22 +252,22 @@ function numericX(value,time=true){
249
252
  return value.getTime();
250
253
  }
251
254
  const number=Number(value);
252
- if(Number.isFinite(number)){
255
+ if(is.finite(number)){
253
256
  return number;
254
257
  }
255
- if(time&&typeof value==='string'){
258
+ if(time&&is.string(value)){
256
259
  const parsed=Date.parse(value);
257
- return Number.isFinite(parsed)?parsed:NaN;
260
+ return is.finite(parsed)?parsed:NaN;
258
261
  }
259
262
  return NaN;
260
263
  }
261
264
 
262
265
  function normalizeChartRows(data=[],options={}){
263
266
  const config=normalizeChartOptions(options);
264
- if(data&&typeof data==='object'&&!Array.isArray(data)){
267
+ if(data&&is.object(data)&&!is.array(data)){
265
268
  data=Object.entries(data);
266
269
  }
267
- if(!Array.isArray(data)){
270
+ if(!is.array(data)){
268
271
  return [];
269
272
  }
270
273
  const normalized=[];
@@ -272,12 +275,12 @@ function normalizeChartRows(data=[],options={}){
272
275
  for(let index=0;index<data.length;index++){
273
276
  const original=data[index];
274
277
  const item=config.mapRow?config.mapRow(original,index):original;
275
- const x=Array.isArray(item)
278
+ const x=is.array(item)
276
279
  ?item[0]
277
280
  :config.xKey
278
281
  ?item?.[config.xKey]
279
282
  :item?.date??item?.timestamp??item?.x;
280
- const y=Array.isArray(item)
283
+ const y=is.array(item)
281
284
  ?item[1]
282
285
  :config.yKey
283
286
  ?item?.[config.yKey]
@@ -287,7 +290,7 @@ function normalizeChartRows(data=[],options={}){
287
290
  const normalizedX=numericX(parsedX,config.time);
288
291
  const normalizedY=Number(parsedY);
289
292
 
290
- if(Number.isFinite(normalizedX)&&Number.isFinite(normalizedY)){
293
+ if(is.finite(normalizedX)&&is.finite(normalizedY)){
291
294
  normalized.push([normalizedX,normalizedY]);
292
295
  }
293
296
  }
@@ -297,7 +300,7 @@ function normalizeChartRows(data=[],options={}){
297
300
  }
298
301
 
299
302
  function normalizeDashboardDefinitions(values=[]){
300
- if(!Array.isArray(values)){
303
+ if(!is.array(values)){
301
304
  throw new TypeError('Dashboard definitions must be an array');
302
305
  }
303
306
  const keys=new Set();
@@ -349,7 +352,7 @@ function normalizeDashboardVisibility(values={}){
349
352
  values=record(values,'Dashboard visibility');
350
353
  const visibility={};
351
354
  for(const [key,value] of Object.entries(values)){
352
- if(typeof value==='boolean'){
355
+ if(is.boolean(value)){
353
356
  visibility[key]=value;
354
357
  }
355
358
  }
@@ -372,7 +375,7 @@ function normalizeDashboardOptions(input={},previous={}){
372
375
  triggerLabel:'trigger'
373
376
  };
374
377
  for(const [source,target] of Object.entries(aliases)){
375
- if(typeof input[source]==='string'){
378
+ if(is.string(input[source])){
376
379
  labels[target]=input[source];
377
380
  }
378
381
  }
@@ -380,7 +383,7 @@ function normalizeDashboardOptions(input={},previous={}){
380
383
  labels,
381
384
  definitions:own(input,'definitions')
382
385
  ?normalizeDashboardDefinitions(input.definitions)
383
- :Array.isArray(previous.definitions)
386
+ :is.array(previous.definitions)
384
387
  ?normalizeDashboardDefinitions(previous.definitions)
385
388
  :[],
386
389
  visibility:own(input,'visibility')
@@ -396,7 +399,7 @@ function effectiveDashboardVisibility(definitions=[],visibility={}){
396
399
  definitions.map(
397
400
  definition=>[
398
401
  definition.key,
399
- typeof visibility[definition.key]==='boolean'
402
+ is.boolean(visibility[definition.key])
400
403
  ?visibility[definition.key]
401
404
  :definition.defaultVisible
402
405
  ]
@@ -405,16 +408,16 @@ function effectiveDashboardVisibility(definitions=[],visibility={}){
405
408
  }
406
409
 
407
410
  function formatAIRuntimeProgress(progress,fallback){
408
- const phase=typeof progress?.phase==='string'&&progress.phase
411
+ const phase=is.string(progress?.phase)&&progress.phase
409
412
  ?progress.phase
410
413
  :fallback;
411
414
  const completed=progress?.completed;
412
415
  const total=progress?.total;
413
- const unit=typeof progress?.unit==='string'
416
+ const unit=is.string(progress?.unit)
414
417
  ?progress.unit
415
418
  :'';
416
- if(!Number.isFinite(completed)
417
- ||!Number.isFinite(total)
419
+ if(!is.finite(completed)
420
+ ||!is.finite(total)
418
421
  ||total<=0
419
422
  ||!unit){
420
423
  return phase;
@@ -429,15 +432,15 @@ function createSTTActivationController({
429
432
  EventClass=globalThis.CustomEvent,
430
433
  eventSource=null
431
434
  }){
432
- if(!host||typeof host.dispatchEvent!=='function'
433
- ||typeof host.requestSTTActivation!=='function'){
435
+ if(!host||!is.function(host.dispatchEvent)
436
+ ||!is.function(host.requestSTTActivation)){
434
437
  throw codedError(
435
438
  'The STT activation host must provide dispatchEvent() and requestSTTActivation().',
436
439
  STT_ACTIVATION_ERROR_CODES.hostInvalid,
437
440
  TypeError
438
441
  );
439
442
  }
440
- if(typeof EventClass!=='function'){
443
+ if(!is.function(EventClass)){
441
444
  throw codedError(
442
445
  'The STT activation event class must be a constructor.',
443
446
  STT_ACTIVATION_ERROR_CODES.eventClassInvalid,
@@ -445,15 +448,15 @@ function createSTTActivationController({
445
448
  );
446
449
  }
447
450
  if(!button
448
- ||typeof button.addEventListener!=='function'
449
- ||typeof button.removeEventListener!=='function'){
451
+ ||!is.function(button.addEventListener)
452
+ ||!is.function(button.removeEventListener)){
450
453
  throw codedError(
451
454
  'The STT activation button must provide addEventListener() and removeEventListener().',
452
455
  STT_ACTIVATION_ERROR_CODES.buttonInvalid,
453
456
  TypeError
454
457
  );
455
458
  }
456
- if(typeof onChange!=='function'){
459
+ if(!is.function(onChange)){
457
460
  throw codedError(
458
461
  'The STT activation onChange callback must be a function.',
459
462
  STT_ACTIVATION_ERROR_CODES.onChangeInvalid,
@@ -462,10 +465,10 @@ function createSTTActivationController({
462
465
  }
463
466
  if(eventSource!==null
464
467
  &&(
465
- typeof eventSource!=='object'
466
- ||typeof eventSource.dispatch!=='function'
467
- ||typeof eventSource.dispose!=='function'
468
- ||typeof eventSource.instanceId!=='string'
468
+ !is.object(eventSource)
469
+ ||!is.function(eventSource.dispatch)
470
+ ||!is.function(eventSource.dispose)
471
+ ||!is.string(eventSource.instanceId)
469
472
  )){
470
473
  throw codedError(
471
474
  'The STT activation event source must be a compatible Arcane event source.',
@@ -489,7 +492,7 @@ function createSTTActivationController({
489
492
  let destroyed=false;
490
493
 
491
494
  function visibleError(error,fallback){
492
- const message=typeof error?.message==='string'
495
+ const message=is.string(error?.message)
493
496
  ?error.message
494
497
  :'';
495
498
  return message||fallback;
@@ -504,9 +507,9 @@ function createSTTActivationController({
504
507
  }
505
508
 
506
509
  function selected(){
507
- return typeof role?.providerId==='string'
510
+ return is.string(role?.providerId)
508
511
  &&role.providerId.length>0
509
- &&typeof role?.modelId==='string'
512
+ &&is.string(role?.modelId)
510
513
  &&role.modelId.length>0;
511
514
  }
512
515
 
@@ -622,7 +625,7 @@ function createSTTActivationController({
622
625
  action:nextAction,
623
626
  reason:STT_ACTIVATION_REASONS.explicitRequest,
624
627
  state:role.state,
625
- roleOperationId:typeof role?.operationId==='string'
628
+ roleOperationId:is.string(role?.operationId)
626
629
  ?role.operationId
627
630
  :null
628
631
  }
@@ -692,7 +695,7 @@ function createSTTActivationController({
692
695
  action:nextAction,
693
696
  code,
694
697
  reason,
695
- causeCode:typeof error?.code==='string'
698
+ causeCode:is.string(error?.code)
696
699
  ?error.code
697
700
  :null
698
701
  }
@@ -851,13 +854,13 @@ function normalizeVoiceOptions(input={},previous={}){
851
854
  transcriptionLabel:'transcription'
852
855
  };
853
856
  for(const [source,target] of Object.entries(aliases)){
854
- if(typeof input[source]==='string'){
857
+ if(is.string(input[source])){
855
858
  labels[target]=input[source];
856
859
  }
857
860
  }
858
861
 
859
862
  const constraints=option(input,previous,'mediaConstraints',{audio:true});
860
- if(!constraints||typeof constraints!=='object'||Array.isArray(constraints)){
863
+ if(!constraints||!is.object(constraints)||is.array(constraints)){
861
864
  throw new TypeError('mediaConstraints must be an object');
862
865
  }
863
866
  if(!own(constraints,'audio')||constraints.audio===false){
@@ -869,14 +872,14 @@ function normalizeVoiceOptions(input={},previous={}){
869
872
  'mimeTypes',
870
873
  ['audio/webm;codecs=opus','audio/mp4','audio/webm']
871
874
  );
872
- if(!Array.isArray(mimeTypes)){
875
+ if(!is.array(mimeTypes)){
873
876
  throw new TypeError('mimeTypes must be an array');
874
877
  }
875
- if(mimeTypes.some(value=>typeof value!=='string')){
878
+ if(mimeTypes.some(value=>!is.string(value))){
876
879
  throw new TypeError('mimeTypes must contain only strings');
877
880
  }
878
881
  const separator=option(input,previous,'separator','\n\n');
879
- if(typeof separator!=='string'){
882
+ if(!is.string(separator)){
880
883
  throw new TypeError('separator must be a string');
881
884
  }
882
885
 
@@ -936,7 +939,7 @@ const MARKDOWN_LABELS=completeValue({
936
939
  });
937
940
 
938
941
  function normalizeMarkdownFormats(values=MARKDOWN_FORMATS){
939
- if(!Array.isArray(values)){
942
+ if(!is.array(values)){
940
943
  throw new TypeError('Markdown formats must be an array');
941
944
  }
942
945
  const ids=new Set();
@@ -984,7 +987,7 @@ function normalizeMarkdownOptions(input={},previous={}){
984
987
  toolbarLabel:'toolbar'
985
988
  };
986
989
  for(const [source,target] of Object.entries(aliases)){
987
- if(typeof input[source]==='string'){
990
+ if(is.string(input[source])){
988
991
  labels[target]=input[source];
989
992
  }
990
993
  }
@@ -1012,11 +1015,11 @@ function applyMarkdownFormat(value='',selectionStart=0,selectionEnd=selectionSta
1012
1015
  const rawEnd=Number(selectionEnd);
1013
1016
  const start=Math.max(
1014
1017
  0,
1015
- Math.min(value.length,Number.isFinite(rawStart)?rawStart:0)
1018
+ Math.min(value.length,is.finite(rawStart)?rawStart:0)
1016
1019
  );
1017
1020
  const end=Math.max(
1018
1021
  start,
1019
- Math.min(value.length,Number.isFinite(rawEnd)?rawEnd:start)
1022
+ Math.min(value.length,is.finite(rawEnd)?rawEnd:start)
1020
1023
  );
1021
1024
  const selected=value.slice(start,end);
1022
1025
  const content=selected||format.placeholder;
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import {recurringChatMessages} from './ChatRecords.js';
2
5
 
3
6
  const FORBIDDEN_REQUEST_FIELDS=new Set([
@@ -12,7 +15,7 @@ const FORBIDDEN_REQUEST_FIELDS=new Set([
12
15
  ]);
13
16
 
14
17
  function isPlainRecord(value){
15
- if(!value||typeof value!=='object'||Array.isArray(value))return false;
18
+ if(!value||!is.object(value)||is.array(value))return false;
16
19
  const prototype=Object.getPrototypeOf(value);
17
20
  return prototype===Object.prototype||prototype===null;
18
21
  }
@@ -23,7 +26,7 @@ function coded(error,code){
23
26
  }
24
27
 
25
28
  function contentText(value,label,{optional=false}={}){
26
- if(typeof value!=='string') throw new TypeError(`${label} must be a string.`);
29
+ if(!is.string(value)) throw new TypeError(`${label} must be a string.`);
27
30
  if(!value.trim()){
28
31
  if(optional) return null;
29
32
  throw new TypeError(`${label} must contain text.`);
@@ -33,7 +36,7 @@ function contentText(value,label,{optional=false}={}){
33
36
 
34
37
  function optionalMetadata(value,label){
35
38
  if(value===undefined||value===null||value==='') return null;
36
- if(typeof value!=='string'){
39
+ if(!is.string(value)){
37
40
  throw coded(new TypeError(`${label} must be a string when provided.`),'AI_CHAT_INVALID_RESPONSE');
38
41
  }
39
42
  if(!value.trim()) return null;
@@ -41,12 +44,12 @@ function optionalMetadata(value,label){
41
44
  }
42
45
 
43
46
  function usageCount(value){
44
- return Number.isSafeInteger(value)&&value>=0?value:null;
47
+ return is.safeInteger(value)&&value>=0?value:null;
45
48
  }
46
49
 
47
50
  function providerUsageCount(value,label){
48
51
  if(value===undefined) return null;
49
- if(!Number.isSafeInteger(value)||value<0){
52
+ if(!is.safeInteger(value)||value<0){
50
53
  throw coded(
51
54
  new TypeError(`${label} must be a nonnegative integer when provided.`),
52
55
  'AI_CHAT_INVALID_RESPONSE',
@@ -57,10 +60,10 @@ function providerUsageCount(value,label){
57
60
 
58
61
  function signalLike(value){
59
62
  return value===undefined||value===null||(
60
- typeof value==='object'
61
- &&typeof value.aborted==='boolean'
62
- &&typeof value.addEventListener==='function'
63
- &&typeof value.removeEventListener==='function'
63
+ is.object(value)
64
+ &&is.boolean(value.aborted)
65
+ &&is.function(value.addEventListener)
66
+ &&is.function(value.removeEventListener)
64
67
  );
65
68
  }
66
69
 
@@ -76,7 +79,7 @@ function assertMessageKeys(value,allowed,label){
76
79
  }
77
80
 
78
81
  function toolCallArgumentMessage(value,label){
79
- if(typeof value!=='string'){
82
+ if(!is.string(value)){
80
83
  throw new TypeError(`${label} must be a JSON string.`);
81
84
  }
82
85
  let argumentsRecord;
@@ -94,7 +97,7 @@ function toolCallArgumentMessage(value,label){
94
97
  'AI_CHAT_INVALID_TOOL_CALL',
95
98
  );
96
99
  }
97
- if(typeof argumentsRecord.message!=='string'||!argumentsRecord.message.trim()){
100
+ if(!is.string(argumentsRecord.message)||!argumentsRecord.message.trim()){
98
101
  throw coded(
99
102
  new TypeError(`${label}.message must contain user-facing text.`),
100
103
  'AI_CHAT_TOOL_MESSAGE_REQUIRED',
@@ -105,7 +108,7 @@ function toolCallArgumentMessage(value,label){
105
108
 
106
109
  function requireToolMessageSchemas(value,label){
107
110
  if(value===undefined) return;
108
- if(!Array.isArray(value)) throw new TypeError(`${label} must be an array.`);
111
+ if(!is.array(value)) throw new TypeError(`${label} must be an array.`);
109
112
  for(let index=0;index<value.length;index++){
110
113
  const tool=value[index];
111
114
  const parameters=tool?.function?.parameters;
@@ -119,9 +122,9 @@ function requireToolMessageSchemas(value,label){
119
122
  ||!isPlainRecord(parameters.properties)
120
123
  ||!isPlainRecord(messageSchema)
121
124
  ||messageSchema.type!=='string'
122
- ||!Number.isInteger(messageSchema.minLength)
125
+ ||!is.integer(messageSchema.minLength)
123
126
  ||messageSchema.minLength<1
124
- ||!Array.isArray(parameters.required)
127
+ ||!is.array(parameters.required)
125
128
  ||!parameters.required.includes('message')
126
129
  ){
127
130
  throw coded(
@@ -141,7 +144,7 @@ function normalizeRequestOptions(value,label){
141
144
  if(forbidden) throw new TypeError(`${label}.${forbidden} is managed by the chat session.`);
142
145
  requireToolMessageSchemas(value.tools,`${label}.tools`);
143
146
  for(const key of ['parallelToolCalls','parallel_tool_calls']){
144
- if(Object.hasOwn(value,key)&&typeof value[key]!=='boolean'){
147
+ if(Object.hasOwn(value,key)&&!is.boolean(value[key])){
145
148
  throw new TypeError(`${label}.${key} must be a boolean when provided.`);
146
149
  }
147
150
  }
@@ -151,12 +154,12 @@ function normalizeRequestOptions(value,label){
151
154
  export function normalizeStructuralToolCall(call,label='Structural tool call'){
152
155
  try{
153
156
  if(!isPlainRecord(call)) throw new TypeError(`${label} must be a plain object.`);
154
- if(typeof call.id!=='string'||!call.id.trim()){
157
+ if(!is.string(call.id)||!call.id.trim()){
155
158
  throw new TypeError(`${label}.id must contain text.`);
156
159
  }
157
160
  if(call.type!=='function') throw new TypeError(`${label}.type must be function.`);
158
161
  if(!isPlainRecord(call.function)) throw new TypeError(`${label}.function must be a plain object.`);
159
- if(typeof call.function.name!=='string'||!call.function.name.trim()){
162
+ if(!is.string(call.function.name)||!call.function.name.trim()){
160
163
  throw new TypeError(`${label}.function.name must contain text.`);
161
164
  }
162
165
  toolCallArgumentMessage(call.function.arguments,`${label}.function.arguments`);
@@ -177,7 +180,7 @@ export function normalizeStructuralToolCall(call,label='Structural tool call'){
177
180
 
178
181
  function normalizeToolCalls(value,label){
179
182
  if(value===undefined) return null;
180
- if(!Array.isArray(value)){
183
+ if(!is.array(value)){
181
184
  throw new TypeError(`${label} must be an array of structural tool calls.`);
182
185
  }
183
186
  const ids=new Set();
@@ -204,13 +207,13 @@ function normalizeMessage(value,label,allowedRoles){
204
207
  if(
205
208
  value.role==='assistant'
206
209
  &&Object.hasOwn(value,'reasoning_content')
207
- &&typeof value.reasoning_content!=='string'
210
+ &&!is.string(value.reasoning_content)
208
211
  ){
209
212
  throw new TypeError(`${label}.reasoning_content must be a string when provided.`);
210
213
  }
211
214
  const hasReasoning=Boolean(
212
215
  value.role==='assistant'
213
- &&typeof value.reasoning_content==='string'
216
+ &&is.string(value.reasoning_content)
214
217
  &&value.reasoning_content.length
215
218
  );
216
219
  let content;
@@ -274,7 +277,7 @@ function publicMessage(value){
274
277
 
275
278
  function normalizeInitialMessages(value){
276
279
  if(value===undefined) return [];
277
- if(!Array.isArray(value)) throw new TypeError('initialMessages must be an array.');
280
+ if(!is.array(value)) throw new TypeError('initialMessages must be an array.');
278
281
  const messages=value.map((item,index)=>normalizeMessage(
279
282
  item,
280
283
  `initialMessages[${index}]`,
@@ -285,14 +288,14 @@ function normalizeInitialMessages(value){
285
288
  }
286
289
 
287
290
  function normalizeInputMessages(value){
288
- if(typeof value==='string'){
291
+ if(is.string(value)){
289
292
  return [normalizeMessage(
290
293
  {role:'user',content:value},
291
294
  'The user message',
292
295
  new Set(['user']),
293
296
  )];
294
297
  }
295
- if(Array.isArray(value)){
298
+ if(is.array(value)){
296
299
  if(!value.length){
297
300
  throw new TypeError('The request tool-result batch must not be empty.');
298
301
  }
@@ -375,7 +378,7 @@ function pendingToolCallIds(messages,validate=false){
375
378
 
376
379
  async function configuredArcaneChat(request){
377
380
  const api=globalThis.Arcane?.ai;
378
- if(typeof api?.chat!=='function'){
381
+ if(!is.function(api?.chat)){
379
382
  throw coded(
380
383
  new Error('The configured Arcane AI chat capability is unavailable.'),
381
384
  'AI_CHAT_UNAVAILABLE',
@@ -427,7 +430,7 @@ function normalizeSessionResponse(response){
427
430
  }
428
431
 
429
432
  function normalizeOpenAICompatibleResponse(response){
430
- if(!Array.isArray(response.choices)||response.choices.length===0){
433
+ if(!is.array(response.choices)||response.choices.length===0){
431
434
  throw coded(
432
435
  new TypeError('The chat provider completion must contain at least one choice.'),
433
436
  'AI_CHAT_INVALID_RESPONSE',
@@ -530,8 +533,8 @@ export default class ConfiguredAIChatSession{
530
533
  const chat=options.chat===undefined?configuredArcaneChat:options.chat;
531
534
  const contextBuilder=options.contextBuilder??null;
532
535
  const request=normalizeRequestOptions(options.request,'request');
533
- if(typeof chat!=='function') throw new TypeError('chat must be a function.');
534
- if(contextBuilder!==null&&typeof contextBuilder!=='function'){
536
+ if(!is.function(chat)) throw new TypeError('chat must be a function.');
537
+ if(contextBuilder!==null&&!is.function(contextBuilder)){
535
538
  throw new TypeError('contextBuilder must be a function when provided.');
536
539
  }
537
540
  const rawSystemPrompt=options.systemPrompt??'';