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.
- package/CHANGELOG.md +44 -0
- package/README.md +42 -19
- package/bin/arcane-test.mjs +16 -13
- package/browser-runtime/ai/browser-speech-artifacts.mjs +41 -38
- package/browser-runtime/ai/browser-speech-providers.mjs +53 -46
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +127 -124
- package/browser-runtime/ai/browser-wasm.mjs +6 -3
- package/browser-runtime/ai/browser-wllama-runtime.mjs +26 -23
- package/browser-runtime/ai/model-controller.mjs +94 -91
- package/browser-runtime/ai/speech-worker-client.mjs +12 -9
- package/browser-runtime/ai/speech-worker-runtime.mjs +56 -53
- package/browser-runtime/dependencies/strong-type/index.js +207 -82
- package/browser-runtime/dependencies/strong-type/package.json +15 -6
- package/browser-runtime/dom-event-instrumentation.mjs +24 -20
- package/browser-runtime/event-manager.mjs +111 -108
- package/browser-runtime/speech-text.mjs +35 -0
- package/docs/architecture.md +1 -1
- package/docs/reference/README.md +8 -8
- package/docs/reference/ai/browser-speech.md +230 -16
- package/docs/reference/ai/twin-cloud.md +1 -1
- package/docs/reference/availability-and-normalization.md +42 -1
- package/docs/reference/cli.md +1 -1
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/inventory/package-api.json +60 -4
- package/docs/reference/inventory/runtime-components.json +2 -2
- package/docs/reference/inventory/runtime-modules.json +29 -31
- package/docs/reference/protocols.md +17 -13
- package/docs/reference/runtime-components.md +6 -5
- package/docs/reference/runtime-modules.md +212 -70
- package/docs/reference/sdk-api.md +197 -11
- package/examples/wasm-ai-demo/index.html +1 -0
- package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
- package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
- package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
- package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
- package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
- package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
- package/node_modules/strong-type/README.md +42 -24
- package/node_modules/strong-type/benchmark/core.js +324 -0
- package/node_modules/strong-type/index.js +207 -82
- package/node_modules/strong-type/package.json +14 -6
- package/package.json +4 -3
- package/runtime/arcane/components/app-bar.html +5 -2
- package/runtime/arcane/components/assistant-panel.html +5 -2
- package/runtime/arcane/components/chart.html +15 -12
- package/runtime/arcane/components/chat.html +118 -141
- package/runtime/arcane/components/dashboard-config.html +6 -3
- package/runtime/arcane/components/data-view.html +4 -1
- package/runtime/arcane/components/directory-picker.html +5 -2
- package/runtime/arcane/components/document-inspector.html +6 -3
- package/runtime/arcane/components/file-drop.html +5 -2
- package/runtime/arcane/components/file-inspector.html +9 -6
- package/runtime/arcane/components/file-manager.html +16 -13
- package/runtime/arcane/components/local-ai-status.html +5 -2
- package/runtime/arcane/components/markdown-document.html +14 -11
- package/runtime/arcane/components/markdown-editor.html +4 -1
- package/runtime/arcane/components/modal.html +4 -1
- package/runtime/arcane/components/output-panel.html +6 -3
- package/runtime/arcane/components/preferences-form.html +4 -1
- package/runtime/arcane/components/record-timeline.html +8 -5
- package/runtime/arcane/components/relationship-board.html +9 -6
- package/runtime/arcane/components/source-code-viewer.html +12 -9
- package/runtime/arcane/components/source-explanation.html +8 -5
- package/runtime/arcane/components/speech.html +24 -21
- package/runtime/arcane/components/summary-strip.html +4 -1
- package/runtime/arcane/components/task-progress.html +6 -3
- package/runtime/arcane/components/terminal-workspace.html +4 -1
- package/runtime/arcane/components/voice-transcription.html +11 -8
- package/runtime/arcane/components/web-navigator.html +5 -2
- package/runtime/arcane/entities/ApiModelRecord.js +5 -2
- package/runtime/arcane/entities/Calculation.js +5 -2
- package/runtime/arcane/entities/Chat.js +33 -33
- package/runtime/arcane/entities/CommunicationMessage.js +4 -1
- package/runtime/arcane/entities/CommunicationThread.js +4 -1
- package/runtime/arcane/entities/File.js +1 -1
- package/runtime/arcane/entities/Image.js +8 -5
- package/runtime/arcane/entities/IntentEnvelope.js +20 -17
- package/runtime/arcane/entities/Preference.js +9 -6
- package/runtime/arcane/entities/Theme.js +5 -2
- package/runtime/arcane/entities/User.js +11 -11
- package/runtime/arcane/entities/Weather.js +5 -2
- package/runtime/arcane/modules/AI.js +503 -187
- package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
- package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
- package/runtime/arcane/modules/AIProviderRuntime.js +117 -92
- package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
- package/runtime/arcane/modules/AIRuntimeState.js +29 -26
- package/runtime/arcane/modules/AnsiText.js +4 -1
- package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
- package/runtime/arcane/modules/AppDataScope.js +8 -5
- package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
- package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
- package/runtime/arcane/modules/AsyncBoundary.js +13 -10
- package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
- package/runtime/arcane/modules/CalculatorEngine.js +5 -2
- package/runtime/arcane/modules/ChatRecords.js +18 -15
- package/runtime/arcane/modules/CommunicationAppController.js +9 -6
- package/runtime/arcane/modules/CommunicationHub.js +9 -6
- package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
- package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
- package/runtime/arcane/modules/ComponentContracts.js +56 -53
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
- package/runtime/arcane/modules/ConversationActionItems.js +15 -12
- package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
- package/runtime/arcane/modules/ConversationTimebox.js +28 -25
- package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
- package/runtime/arcane/modules/DBLS.js +7 -4
- package/runtime/arcane/modules/DBOPFS.js +7 -7
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
- package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
- package/runtime/arcane/modules/DirectoryPicker.js +8 -5
- package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
- package/runtime/arcane/modules/DocumentNavigation.js +7 -4
- package/runtime/arcane/modules/Errors.js +28 -25
- package/runtime/arcane/modules/HTMLImport.js +7 -4
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
- package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
- package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
- package/runtime/arcane/modules/MD.js +1 -1
- package/runtime/arcane/modules/Mail.js +46 -43
- package/runtime/arcane/modules/MailOutbox.mjs +48 -45
- package/runtime/arcane/modules/MailTransport.mjs +29 -26
- package/runtime/arcane/modules/MarkdownSpeech.js +1 -50
- package/runtime/arcane/modules/MemoryRecords.js +7 -4
- package/runtime/arcane/modules/MessageAdvisory.js +6 -3
- package/runtime/arcane/modules/ModelDefinition.js +7 -4
- package/runtime/arcane/modules/Ollama.js +6 -3
- package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
- package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
- package/runtime/arcane/modules/PreferenceStore.js +20 -17
- package/runtime/arcane/modules/PreparedSpeech.js +485 -0
- package/runtime/arcane/modules/Questionnaire.js +6 -3
- package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
- package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
- package/runtime/arcane/modules/RecordReviewStore.js +12 -9
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
- package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
- package/runtime/arcane/modules/ScreenCapture.js +20 -17
- package/runtime/arcane/modules/SpeechPlayback.js +61 -28
- package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
- package/runtime/arcane/modules/SystemAppearance.js +5 -2
- package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
- package/runtime/arcane/modules/TerminalClient.js +14 -11
- package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
- package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
- package/runtime/arcane/modules/ThemeManager.js +5 -2
- package/runtime/arcane/modules/TimeGuard.js +1 -1
- package/runtime/arcane/modules/ToolCallRouter.js +11 -8
- package/runtime/arcane/modules/WaitForComponent.js +19 -16
- package/runtime/arcane/modules/YouTubeMedia.js +4 -1
- package/runtime/strong-type/index.js +1276 -352
- package/runtime/strong-type/package.json +69 -45
- package/src/app-descriptor.mjs +17 -14
- package/src/application-tests.mjs +4 -1
- package/src/cli/main.mjs +8 -5
- package/src/constants.mjs +4 -1
- package/src/dev-server.mjs +8 -5
- package/src/doctor.mjs +5 -2
- package/src/dom-event-instrumentation.mjs +24 -20
- package/src/errors.mjs +7 -3
- package/src/event-manager.mjs +111 -108
- package/src/event-queue.mjs +8 -5
- package/src/events.mjs +12 -9
- package/src/import-map.mjs +36 -27
- package/src/installed-sdk-runtime.mjs +4 -1
- package/src/integrated-provider-loader.mjs +9 -6
- package/src/mail-credentials.mjs +16 -13
- package/src/mail-server.mjs +53 -50
- package/src/mail.mjs +18 -15
- package/src/native-plan.mjs +12 -9
- package/src/native-provider-loader.mjs +7 -4
- package/src/packager/core.mjs +18 -15
- package/src/process.mjs +6 -3
- package/src/release-bundle.mjs +14 -11
- package/src/runtime.mjs +4 -1
- package/src/scaffold.mjs +9 -6
- package/src/sdk-browser-runtime.mjs +4 -1
- package/src/source-server.mjs +17 -14
- package/src/targets/index.mjs +5 -2
- package/src/templates/workspace-template.mjs +13 -6
- package/src/testing-loader.mjs +7 -4
- package/src/testing.mjs +10 -7
- package/src/toolchain.mjs +13 -10
- package/src/update-check.mjs +9 -6
- package/src/workspace-operation-lock.mjs +14 -11
- package/src/workspace-runtime.mjs +4 -1
- package/src/workspace.mjs +17 -14
- 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 {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
3
6
|
import GifEncoder from './GifEncoder.js';
|
|
@@ -163,10 +166,10 @@ function signalLike(value){
|
|
|
163
166
|
return value===undefined
|
|
164
167
|
||value===null
|
|
165
168
|
||(
|
|
166
|
-
|
|
167
|
-
&&
|
|
168
|
-
&&
|
|
169
|
-
&&
|
|
169
|
+
is.object(value)
|
|
170
|
+
&&is.boolean(value.aborted)
|
|
171
|
+
&&is.function(value.addEventListener)
|
|
172
|
+
&&is.function(value.removeEventListener)
|
|
170
173
|
);
|
|
171
174
|
}
|
|
172
175
|
|
|
@@ -190,7 +193,7 @@ function abortError(cause){
|
|
|
190
193
|
|
|
191
194
|
function optionsRecord(value,label){
|
|
192
195
|
if(value===undefined)return {};
|
|
193
|
-
if(!value
|
|
196
|
+
if(!value||!is.object(value)||is.array(value)){
|
|
194
197
|
throw captureError(
|
|
195
198
|
SCREEN_CAPTURE_ERRORS.optionsRecordRejected,
|
|
196
199
|
label+' must be an object.',
|
|
@@ -211,7 +214,7 @@ function optionsRecord(value,label){
|
|
|
211
214
|
|
|
212
215
|
function admittedOperationId(value){
|
|
213
216
|
if(value===undefined||value===null)return null;
|
|
214
|
-
if(
|
|
217
|
+
if(!is.string(value)
|
|
215
218
|
||value.trim()!==value
|
|
216
219
|
||!value){
|
|
217
220
|
throw captureError(
|
|
@@ -225,7 +228,7 @@ function admittedOperationId(value){
|
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
function imageResultType(blob){
|
|
228
|
-
const reported=
|
|
231
|
+
const reported=is.string(blob?.type)?blob.type.trim().toLowerCase():'';
|
|
229
232
|
if(!reported)return SCREEN_CAPTURE_IMAGE_TYPE_FALLBACK;
|
|
230
233
|
const subtype=reported.startsWith('image/')
|
|
231
234
|
?reported.slice('image/'.length).split(';',1)[0]
|
|
@@ -252,7 +255,7 @@ function supportedRecorderType(Recorder){
|
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
function reportDetachedError(error){
|
|
255
|
-
if(
|
|
258
|
+
if(is.function(globalThis.reportError))globalThis.reportError(error);
|
|
256
259
|
else arcaneLogging.error?.(error);
|
|
257
260
|
}
|
|
258
261
|
|
|
@@ -436,7 +439,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
436
439
|
if(this.#events.disposed)return null;
|
|
437
440
|
const compatibilityDetail={
|
|
438
441
|
...detail,
|
|
439
|
-
mode:
|
|
442
|
+
mode:is.string(detail?.mode)?detail.mode:operation.mode,
|
|
440
443
|
operationId:operation.id,
|
|
441
444
|
reason,
|
|
442
445
|
...(code?{code}:{})
|
|
@@ -527,7 +530,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
527
530
|
|
|
528
531
|
#attachTrackEnd(operation,stream){
|
|
529
532
|
for(const track of stream?.getTracks?.()||[]){
|
|
530
|
-
if(
|
|
533
|
+
if(!is.function(track?.addEventListener))continue;
|
|
531
534
|
const owner=this;
|
|
532
535
|
function stopEndedScreenCaptureTrack(){
|
|
533
536
|
if(!owner.#isCurrent(operation))return;
|
|
@@ -782,7 +785,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
782
785
|
);
|
|
783
786
|
this.#assertCurrent(operation);
|
|
784
787
|
stage='image-encoding';
|
|
785
|
-
const type=
|
|
788
|
+
const type=is.string(options.type)&&options.type.trim()
|
|
786
789
|
?options.type.trim()
|
|
787
790
|
:'image/png';
|
|
788
791
|
const blob=await this.#canvasBlob(
|
|
@@ -990,7 +993,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
990
993
|
|
|
991
994
|
#startGifFrameSampling(operation,context,frameDelay){
|
|
992
995
|
const requestedDelay=Number(frameDelay);
|
|
993
|
-
const explicitDelay=
|
|
996
|
+
const explicitDelay=is.finite(requestedDelay)&&requestedDelay>0
|
|
994
997
|
?requestedDelay
|
|
995
998
|
:null;
|
|
996
999
|
let active=true;
|
|
@@ -1000,10 +1003,10 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1000
1003
|
if(explicitDelay!==null){
|
|
1001
1004
|
requestFrame=callback=>setTimeout(()=>callback(monotonicNow()),explicitDelay);
|
|
1002
1005
|
cancelFrame=handle=>clearTimeout(handle);
|
|
1003
|
-
}else if(
|
|
1006
|
+
}else if(is.function(operation.video?.requestVideoFrameCallback)){
|
|
1004
1007
|
requestFrame=callback=>operation.video.requestVideoFrameCallback(callback);
|
|
1005
1008
|
cancelFrame=handle=>operation.video.cancelVideoFrameCallback?.(handle);
|
|
1006
|
-
}else if(
|
|
1009
|
+
}else if(is.function(globalThis.requestAnimationFrame)){
|
|
1007
1010
|
requestFrame=callback=>globalThis.requestAnimationFrame(callback);
|
|
1008
1011
|
cancelFrame=handle=>globalThis.cancelAnimationFrame?.(handle);
|
|
1009
1012
|
}else{
|
|
@@ -1065,7 +1068,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1065
1068
|
operation.frames=[];
|
|
1066
1069
|
operation.frameTimes=[];
|
|
1067
1070
|
const requestedDelay=Number(options.frameDelay);
|
|
1068
|
-
operation.gifDelay=
|
|
1071
|
+
operation.gifDelay=is.finite(requestedDelay)&&requestedDelay>0
|
|
1069
1072
|
?requestedDelay
|
|
1070
1073
|
:0;
|
|
1071
1074
|
operation.startedAt=Date.now();
|
|
@@ -1261,7 +1264,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1261
1264
|
if(options.signal?.aborted)throw abortError(options.signal.reason);
|
|
1262
1265
|
const width=Number(video.videoWidth);
|
|
1263
1266
|
const height=Number(video.videoHeight);
|
|
1264
|
-
if(!
|
|
1267
|
+
if(!is.finite(width)||width<=0||!is.finite(height)||height<=0){
|
|
1265
1268
|
throw captureError(
|
|
1266
1269
|
SCREEN_CAPTURE_ERRORS.displayMetadataRejected,
|
|
1267
1270
|
'The selected display reported invalid frame dimensions.'
|
|
@@ -1370,7 +1373,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1370
1373
|
TypeError
|
|
1371
1374
|
);
|
|
1372
1375
|
}
|
|
1373
|
-
const mode=
|
|
1376
|
+
const mode=is.string(detail?.mode)
|
|
1374
1377
|
?detail.mode
|
|
1375
1378
|
:(this.#operation?.mode??'capture');
|
|
1376
1379
|
const operation=this.#operation??{
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
5
|
+
import {stripSpeechFormatting} from 'arcane-os/speech-text';
|
|
2
6
|
|
|
7
|
+
const SPEECH_VOICE_OPTIONS=[
|
|
8
|
+
{value:'alloy',label:'Alloy'},
|
|
9
|
+
{value:'ash',label:'Ash'},
|
|
10
|
+
{value:'ballad',label:'Ballad'},
|
|
11
|
+
{value:'coral',label:'Coral'},
|
|
12
|
+
{value:'echo',label:'Echo'},
|
|
13
|
+
{value:'fable',label:'Fable'},
|
|
14
|
+
{value:'nova',label:'Nova'},
|
|
15
|
+
{value:'onyx',label:'Onyx'},
|
|
16
|
+
{value:'sage',label:'Sage'},
|
|
17
|
+
{value:'shimmer',label:'Shimmer'}
|
|
18
|
+
];
|
|
3
19
|
const SUPERSEDED={superseded:true};
|
|
4
20
|
const SPEECH_PLAYBACK_STATE_EVENT='speech-playback-state';
|
|
5
21
|
const SPEECH_PLAYBACK_FAILURE_REASONS={
|
|
@@ -17,21 +33,25 @@ const WAV_AUDIO_CONTENT_TYPES=new Set([
|
|
|
17
33
|
]);
|
|
18
34
|
const queuesBySpeechClient=new WeakMap();
|
|
19
35
|
|
|
36
|
+
const SPEECH_VOICE_ALIASES=new Set(
|
|
37
|
+
SPEECH_VOICE_OPTIONS.map(function voiceAlias(option){return option.value;})
|
|
38
|
+
);
|
|
39
|
+
|
|
20
40
|
function splitSpeechText(value=''){
|
|
21
41
|
const text=String(value??'');
|
|
22
42
|
return text.trim()?[text]:[];
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
function normalizeParts(parts,defaultVoice=null,defaultSpeed=1){
|
|
26
|
-
if(!
|
|
46
|
+
if(!is.array(parts)||!parts.length){
|
|
27
47
|
throw new TypeError('Narration text cannot be blank. The full visual content remains available.');
|
|
28
48
|
}
|
|
29
49
|
return parts.map(function normalizePart(part){
|
|
30
|
-
const candidate=
|
|
50
|
+
const candidate=is.string(part)?{input:part}:part;
|
|
31
51
|
const input=String(candidate?.input??'');
|
|
32
52
|
if(!input.trim())throw new TypeError('Speech segments cannot be blank. The full visual content remains available.');
|
|
33
53
|
const pauseAfterMs=Number(candidate?.pauseAfterMs??0);
|
|
34
|
-
if(!
|
|
54
|
+
if(!is.finite(pauseAfterMs)||pauseAfterMs<0){
|
|
35
55
|
throw new RangeError('Speech segment pauses must be a nonnegative number of milliseconds.');
|
|
36
56
|
}
|
|
37
57
|
const voiceValue=candidate?.voice??defaultVoice;
|
|
@@ -40,7 +60,7 @@ function normalizeParts(parts,defaultVoice=null,defaultSpeed=1){
|
|
|
40
60
|
if(voiceValue!==null&&voiceValue!==undefined&&!voice.trim()){
|
|
41
61
|
throw new TypeError('Speech segment voice cannot be blank.');
|
|
42
62
|
}
|
|
43
|
-
if(!
|
|
63
|
+
if(!is.finite(speed)||speed<=0)throw new RangeError('Every speech segment requires a positive speech speed.');
|
|
44
64
|
return {
|
|
45
65
|
input,
|
|
46
66
|
pauseAfterMs,
|
|
@@ -67,7 +87,7 @@ function synthesizedAudioContractError(cause=null){
|
|
|
67
87
|
|
|
68
88
|
function normalizeAudioContentType(value,fallback=null){
|
|
69
89
|
const candidate=value===undefined||value===null?fallback:value;
|
|
70
|
-
if(
|
|
90
|
+
if(!is.string(candidate)||!candidate.trim()){
|
|
71
91
|
throw synthesizedAudioContractError();
|
|
72
92
|
}
|
|
73
93
|
const contentType=candidate.split(';',1)[0].trim().toLowerCase();
|
|
@@ -86,7 +106,7 @@ function speechAudioBytes(value){
|
|
|
86
106
|
if(value instanceof ArrayBuffer){
|
|
87
107
|
return new Uint8Array(value.slice(0));
|
|
88
108
|
}
|
|
89
|
-
if(
|
|
109
|
+
if(is.arrayBufferView(value)){
|
|
90
110
|
return new Uint8Array(
|
|
91
111
|
value.buffer.slice(
|
|
92
112
|
value.byteOffset,
|
|
@@ -186,23 +206,23 @@ async function validatedSpeechBytes(bytes,contentType='audio/wav'){
|
|
|
186
206
|
async function playableSpeechBlob(response){
|
|
187
207
|
try{
|
|
188
208
|
if(response instanceof Blob)return validatedSpeechBlob(response);
|
|
189
|
-
if(response instanceof ArrayBuffer||
|
|
209
|
+
if(response instanceof ArrayBuffer||is.arrayBufferView(response)){
|
|
190
210
|
return validatedSpeechBytes(speechAudioBytes(response));
|
|
191
211
|
}
|
|
192
|
-
if(response&&
|
|
212
|
+
if(response&&is.object(response)){
|
|
193
213
|
if(response.audio instanceof Blob){
|
|
194
214
|
return validatedSpeechBlob(
|
|
195
215
|
response.audio,
|
|
196
216
|
response.contentType
|
|
197
217
|
);
|
|
198
218
|
}
|
|
199
|
-
if(response.audio instanceof ArrayBuffer||
|
|
219
|
+
if(response.audio instanceof ArrayBuffer||is.arrayBufferView(response.audio)){
|
|
200
220
|
return validatedSpeechBytes(
|
|
201
221
|
speechAudioBytes(response.audio),
|
|
202
222
|
response.contentType
|
|
203
223
|
);
|
|
204
224
|
}
|
|
205
|
-
if(
|
|
225
|
+
if(is.string(response.audioBase64)&&response.audioBase64){
|
|
206
226
|
return validatedSpeechBytes(
|
|
207
227
|
base64Bytes(response.audioBase64),
|
|
208
228
|
response.contentType
|
|
@@ -221,7 +241,7 @@ async function playableSpeechBlob(response){
|
|
|
221
241
|
|
|
222
242
|
function speechPlaybackFailureReason(error){
|
|
223
243
|
if(error?.name==='AbortError')return 'speech-synthesis-cancelled';
|
|
224
|
-
if(
|
|
244
|
+
if(is.string(error?.code)
|
|
225
245
|
&&Object.hasOwn(SPEECH_PLAYBACK_FAILURE_REASONS,error.code)){
|
|
226
246
|
return SPEECH_PLAYBACK_FAILURE_REASONS[error.code];
|
|
227
247
|
}
|
|
@@ -278,7 +298,7 @@ class LatestSpeechQueue{
|
|
|
278
298
|
}
|
|
279
299
|
|
|
280
300
|
function queueFor(speech){
|
|
281
|
-
if((
|
|
301
|
+
if((!is.object(speech)||speech===null)&&!is.function(speech)){
|
|
282
302
|
throw new TypeError('A local speech client is required.');
|
|
283
303
|
}
|
|
284
304
|
let queue=queuesBySpeechClient.get(speech);
|
|
@@ -290,15 +310,15 @@ function queueFor(speech){
|
|
|
290
310
|
}
|
|
291
311
|
|
|
292
312
|
function providerSpeechCapacity(speech){
|
|
293
|
-
if(
|
|
313
|
+
if(!is.function(speech?.fetchTTS))return null;
|
|
294
314
|
try{
|
|
295
315
|
const providerRuntime=speech.providerRuntime;
|
|
296
|
-
if(!providerRuntime
|
|
316
|
+
if(!providerRuntime||!is.function(providerRuntime.status))return null;
|
|
297
317
|
const capacity=providerRuntime.status(
|
|
298
318
|
'tts',
|
|
299
319
|
{execution:true}
|
|
300
320
|
)?.execution?.maxConcurrentRequests;
|
|
301
|
-
return
|
|
321
|
+
return is.safeInteger(capacity)&&capacity>0?capacity:null;
|
|
302
322
|
}catch{
|
|
303
323
|
// Clients without inspectable provider capacity retain serialized admission.
|
|
304
324
|
return null;
|
|
@@ -401,16 +421,17 @@ class SpeechPlayback{
|
|
|
401
421
|
voice=null,
|
|
402
422
|
responseFormat=null,
|
|
403
423
|
speed=1,
|
|
424
|
+
onState=function noop(){},
|
|
404
425
|
createObjectURL,
|
|
405
426
|
revokeObjectURL,
|
|
406
427
|
delay,
|
|
407
428
|
messages={}
|
|
408
429
|
}={}){
|
|
409
|
-
if(!audio
|
|
430
|
+
if(!audio||!is.function(audio.addEventListener)){
|
|
410
431
|
throw new TypeError('SpeechPlayback requires an audio element.');
|
|
411
432
|
}
|
|
412
433
|
const normalizedSpeed=Number(speed);
|
|
413
|
-
if(!
|
|
434
|
+
if(!is.finite(normalizedSpeed)||normalizedSpeed<=0){
|
|
414
435
|
throw new RangeError('SpeechPlayback speed must be a positive number.');
|
|
415
436
|
}
|
|
416
437
|
this.audio=audio;
|
|
@@ -422,6 +443,7 @@ class SpeechPlayback{
|
|
|
422
443
|
eventTypes:[SPEECH_PLAYBACK_STATE_EVENT]
|
|
423
444
|
}
|
|
424
445
|
);
|
|
446
|
+
this.onState=onState;
|
|
425
447
|
this.createObjectURL=createObjectURL||function createAudioURL(blob){return URL.createObjectURL(blob);};
|
|
426
448
|
this.revokeObjectURL=revokeObjectURL||function revokeAudioURL(url){URL.revokeObjectURL(url);};
|
|
427
449
|
this.delay=delay||function playbackDelay(duration,signal){
|
|
@@ -477,7 +499,7 @@ class SpeechPlayback{
|
|
|
477
499
|
|
|
478
500
|
message(name,details={}){
|
|
479
501
|
const value=this.messages[name];
|
|
480
|
-
return String(
|
|
502
|
+
return String(is.function(value)?value(details):value||'');
|
|
481
503
|
}
|
|
482
504
|
|
|
483
505
|
nextOperationId(){
|
|
@@ -515,6 +537,12 @@ class SpeechPlayback{
|
|
|
515
537
|
}
|
|
516
538
|
);
|
|
517
539
|
}
|
|
540
|
+
try{
|
|
541
|
+
this.onState(detail);
|
|
542
|
+
}catch(error){
|
|
543
|
+
if(is.function(globalThis.reportError))globalThis.reportError(error);
|
|
544
|
+
else console.error(error);
|
|
545
|
+
}
|
|
518
546
|
return detail;
|
|
519
547
|
}
|
|
520
548
|
|
|
@@ -522,8 +550,8 @@ class SpeechPlayback{
|
|
|
522
550
|
return Boolean(
|
|
523
551
|
this.speech
|
|
524
552
|
&&(
|
|
525
|
-
|
|
526
|
-
||
|
|
553
|
+
is.function(this.speech.fetchTTS)
|
|
554
|
+
||is.function(this.speech.synthesize)
|
|
527
555
|
)
|
|
528
556
|
);
|
|
529
557
|
}
|
|
@@ -569,21 +597,23 @@ class SpeechPlayback{
|
|
|
569
597
|
}
|
|
570
598
|
|
|
571
599
|
async requestSpeech(part,signal){
|
|
600
|
+
const input=stripSpeechFormatting(part.input);
|
|
601
|
+
const preparation={speechInputPrepared:true};
|
|
572
602
|
const payload={
|
|
573
|
-
input
|
|
603
|
+
input,
|
|
574
604
|
speed:part.speed,
|
|
575
605
|
...(this.model?{model:this.model}:{}),
|
|
576
606
|
...(part.voice?{voice:part.voice}:{}),
|
|
577
607
|
...(this.responseFormat?{responseFormat:this.responseFormat}:{})
|
|
578
608
|
};
|
|
579
|
-
if(
|
|
609
|
+
if(is.function(this.speech?.fetchTTS)){
|
|
580
610
|
return playableSpeechBlob(
|
|
581
|
-
await this.speech.fetchTTS(payload,signal)
|
|
611
|
+
await this.speech.fetchTTS(payload,signal,preparation)
|
|
582
612
|
);
|
|
583
613
|
}
|
|
584
|
-
if(
|
|
614
|
+
if(is.function(this.speech?.synthesize)){
|
|
585
615
|
return playableSpeechBlob(
|
|
586
|
-
await this.speech.synthesize(payload,{signal})
|
|
616
|
+
await this.speech.synthesize(payload,{signal},preparation)
|
|
587
617
|
);
|
|
588
618
|
}
|
|
589
619
|
const error=new Error(this.message('unavailable'));
|
|
@@ -756,7 +786,7 @@ class SpeechPlayback{
|
|
|
756
786
|
throw error;
|
|
757
787
|
}
|
|
758
788
|
const numericSpeed=Number(speed);
|
|
759
|
-
if(!
|
|
789
|
+
if(!is.finite(numericSpeed)||numericSpeed<=0)throw new RangeError('Speech speed must be a positive number.');
|
|
760
790
|
const selectedModel=model===null||model===undefined
|
|
761
791
|
?null
|
|
762
792
|
:String(model);
|
|
@@ -780,7 +810,7 @@ class SpeechPlayback{
|
|
|
780
810
|
this.voice=selectedVoice;
|
|
781
811
|
this.responseFormat=selectedResponseFormat;
|
|
782
812
|
normalized=normalizeParts(
|
|
783
|
-
|
|
813
|
+
is.function(parts)?parts():parts,
|
|
784
814
|
selectedVoice,
|
|
785
815
|
numericSpeed
|
|
786
816
|
);
|
|
@@ -980,7 +1010,7 @@ class SpeechPlayback{
|
|
|
980
1010
|
|
|
981
1011
|
fail(error){
|
|
982
1012
|
const reason=speechPlaybackFailureReason(error);
|
|
983
|
-
const code=
|
|
1013
|
+
const code=is.string(error?.code)&&error.code.trim()
|
|
984
1014
|
?error.code.trim()
|
|
985
1015
|
:reason==='speech-playback-request-contract-mismatch'
|
|
986
1016
|
?'ARCANE_SPEECH_PLAYBACK_REQUEST_CONTRACT_MISMATCH'
|
|
@@ -1002,12 +1032,15 @@ class SpeechPlayback{
|
|
|
1002
1032
|
this.audio.removeEventListener('error',this.boundError);
|
|
1003
1033
|
this.destroyed=true;
|
|
1004
1034
|
this.events.dispose();
|
|
1035
|
+
this.onState=function destroyedSpeechPlaybackStateObserver(){};
|
|
1005
1036
|
return true;
|
|
1006
1037
|
}
|
|
1007
1038
|
}
|
|
1008
1039
|
|
|
1009
1040
|
export {
|
|
1010
1041
|
SPEECH_PLAYBACK_STATE_EVENT,
|
|
1042
|
+
SPEECH_VOICE_ALIASES,
|
|
1043
|
+
SPEECH_VOICE_OPTIONS,
|
|
1011
1044
|
SpeechPlayback,
|
|
1012
1045
|
splitSpeechText
|
|
1013
1046
|
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import DocumentLexicalSearch,{
|
|
2
5
|
documentContextExcerpt,
|
|
3
6
|
documentSearchTokens,
|
|
@@ -16,8 +19,8 @@ const MEDIA_TYPES=new Set(['text/markdown','text/plain']);
|
|
|
16
19
|
|
|
17
20
|
function isPlainRecord(value){
|
|
18
21
|
return Boolean(value)
|
|
19
|
-
&&
|
|
20
|
-
&&!
|
|
22
|
+
&&is.object(value)
|
|
23
|
+
&&!is.array(value)
|
|
21
24
|
&&Object.getPrototypeOf(value)===Object.prototype;
|
|
22
25
|
}
|
|
23
26
|
|
|
@@ -36,7 +39,7 @@ function assertKnownKeys(value,allowed,label,code='STATIC_DOCUMENT_INVALID_CATAL
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
function structuralInteger(value,label,{minimum=0,maximum=null}={}){
|
|
39
|
-
if(!
|
|
42
|
+
if(!is.safeInteger(value)||value<minimum||(maximum!==null&&value>maximum)){
|
|
40
43
|
const range=maximum===null?`${minimum} or greater`:`${minimum} through ${maximum}`;
|
|
41
44
|
fail(`${label} must be a safe integer ${range}.`,'STATIC_DOCUMENT_INVALID_VALUE',RangeError);
|
|
42
45
|
}
|
|
@@ -45,7 +48,7 @@ function structuralInteger(value,label,{minimum=0,maximum=null}={}){
|
|
|
45
48
|
|
|
46
49
|
function normalizedText(value,label,{optional=false}={}){
|
|
47
50
|
if(optional&&(value===undefined||value===null||value==='')) return '';
|
|
48
|
-
if(
|
|
51
|
+
if(!is.string(value)) fail(`${label} must be a string.`,'STATIC_DOCUMENT_INVALID_CATALOG');
|
|
49
52
|
if(!value.trim()&&!optional) fail(`${label} cannot be empty.`,'STATIC_DOCUMENT_INVALID_CATALOG');
|
|
50
53
|
if(CONTROL_CHARACTERS.test(value)) fail(`${label} cannot contain control characters.`,'STATIC_DOCUMENT_INVALID_CATALOG');
|
|
51
54
|
if(value!==value.normalize('NFC')) fail(`${label} must use Unicode NFC normalization.`,'STATIC_DOCUMENT_INVALID_CATALOG');
|
|
@@ -93,7 +96,7 @@ function relativePath(value,label='Document path'){
|
|
|
93
96
|
|
|
94
97
|
function normalizeTags(value){
|
|
95
98
|
if(value===undefined) return [];
|
|
96
|
-
if(!
|
|
99
|
+
if(!is.array(value))fail('Document tags must be an array.','STATIC_DOCUMENT_INVALID_CATALOG');
|
|
97
100
|
const seen=new Set();
|
|
98
101
|
const tags=value.map((item,index)=>{
|
|
99
102
|
const tag=normalizedText(item,`Document tag ${index+1}`);
|
|
@@ -107,7 +110,7 @@ function normalizeTags(value){
|
|
|
107
110
|
|
|
108
111
|
function normalizeTextList(value,label){
|
|
109
112
|
if(value===undefined) return [];
|
|
110
|
-
if(!
|
|
113
|
+
if(!is.array(value))fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_CATALOG');
|
|
111
114
|
const seen=new Set();
|
|
112
115
|
const values=value.map(function normalizeTextListEntry(item,index){
|
|
113
116
|
const text=normalizedText(item,`${label} entry ${index+1}`);
|
|
@@ -149,7 +152,7 @@ function normalizeLanguage(value,label){
|
|
|
149
152
|
|
|
150
153
|
function normalizeSearchTerms(value,label){
|
|
151
154
|
if(value===undefined) return [];
|
|
152
|
-
if(!
|
|
155
|
+
if(!is.array(value))fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_CATALOG');
|
|
153
156
|
const seen=new Set();
|
|
154
157
|
const terms=value.map((item,index)=>{
|
|
155
158
|
const term=normalizedText(item,`${label} entry ${index+1}`);
|
|
@@ -163,7 +166,7 @@ function normalizeSearchTerms(value,label){
|
|
|
163
166
|
|
|
164
167
|
function normalizeHeadings(value){
|
|
165
168
|
if(value===undefined) return [];
|
|
166
|
-
if(!
|
|
169
|
+
if(!is.array(value))fail('Document headings must be an array.','STATIC_DOCUMENT_INVALID_CATALOG');
|
|
167
170
|
const seen=new Set();
|
|
168
171
|
const headings=value.map((item,index)=>{
|
|
169
172
|
if(!isPlainRecord(item)) fail(`Document heading ${index+1} must be a plain object.`,'STATIC_DOCUMENT_INVALID_CATALOG');
|
|
@@ -184,7 +187,7 @@ function normalizeHeadings(value){
|
|
|
184
187
|
|
|
185
188
|
function normalizePathList(value,label){
|
|
186
189
|
if(value===undefined) return [];
|
|
187
|
-
if(!
|
|
190
|
+
if(!is.array(value))fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_CATALOG');
|
|
188
191
|
const seen=new Set();
|
|
189
192
|
const paths=value.map((item,index)=>{
|
|
190
193
|
const path=relativePath(item,`${label} entry ${index+1}`);
|
|
@@ -293,7 +296,7 @@ function normalizeStaticDocumentCatalog(input,options={}){
|
|
|
293
296
|
if(!isPlainRecord(options)) fail('Catalog normalization options must be a plain object.','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
294
297
|
if(!isPlainRecord(input)) fail('Static document catalog must be a plain object.','STATIC_DOCUMENT_INVALID_CATALOG');
|
|
295
298
|
assertKnownKeys(input,new Set(['documents','version']),'Static document catalog');
|
|
296
|
-
if(!
|
|
299
|
+
if(!is.array(input.documents)) fail('Static document catalog documents must be an array.','STATIC_DOCUMENT_INVALID_CATALOG');
|
|
297
300
|
const records=input.documents.map((record,index)=>normalizeRecord(record,index));
|
|
298
301
|
const ids=new Set();
|
|
299
302
|
const paths=new Set();
|
|
@@ -384,11 +387,11 @@ function normalizeOptions(input){
|
|
|
384
387
|
cacheTimeoutMs:optionalTimeout(input.cacheTimeoutMs,'cacheTimeoutMs'),
|
|
385
388
|
fetchTimeoutMs:optionalTimeout(input.fetchTimeoutMs,'fetchTimeoutMs'),
|
|
386
389
|
});
|
|
387
|
-
const fetchImpl=input.fetchImpl??(
|
|
388
|
-
if(fetchImpl!==null
|
|
389
|
-
if(input.onCacheError!==undefined
|
|
390
|
+
const fetchImpl=input.fetchImpl??(is.function(globalThis.fetch)?globalThis.fetch.bind(globalThis):null);
|
|
391
|
+
if(fetchImpl!==null&&!is.function(fetchImpl)) fail('fetchImpl must be a function when provided.','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
392
|
+
if(input.onCacheError!==undefined&&!is.function(input.onCacheError)) fail('onCacheError must be a function when provided.','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
390
393
|
const cache=input.cache??null;
|
|
391
|
-
if(cache!==null&&(
|
|
394
|
+
if(cache!==null&&(!is.object(cache)||!is.function(cache.get)||!is.function(cache.set))){
|
|
392
395
|
fail('cache must expose get(key) and set(key, value).','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
393
396
|
}
|
|
394
397
|
return completeValue({
|
|
@@ -411,7 +414,7 @@ function lineNumberAt(value,offset){
|
|
|
411
414
|
}
|
|
412
415
|
|
|
413
416
|
function queryText(value){
|
|
414
|
-
if(
|
|
417
|
+
if(!is.string(value)) fail('Search query must be a string.','STATIC_DOCUMENT_INVALID_QUERY');
|
|
415
418
|
const query=value.trim();
|
|
416
419
|
if(CONTROL_CHARACTERS.test(query)){
|
|
417
420
|
fail('Search query must be plain text.','STATIC_DOCUMENT_INVALID_QUERY');
|
|
@@ -421,7 +424,7 @@ function queryText(value){
|
|
|
421
424
|
|
|
422
425
|
function normalizeFilter(value,label){
|
|
423
426
|
if(value===undefined) return null;
|
|
424
|
-
if(!
|
|
427
|
+
if(!is.array(value)) fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_QUERY');
|
|
425
428
|
const normalized=value.map((item,index)=>normalizedText(item,`${label} entry ${index+1}`).toLowerCase());
|
|
426
429
|
return new Set(normalized);
|
|
427
430
|
}
|
|
@@ -442,19 +445,19 @@ function staticDocumentCacheKey(version,id){
|
|
|
442
445
|
}
|
|
443
446
|
|
|
444
447
|
async function responseText(response){
|
|
445
|
-
if(
|
|
446
|
-
if(response instanceof ArrayBuffer||
|
|
448
|
+
if(is.string(response))return response;
|
|
449
|
+
if(response instanceof ArrayBuffer||is.arrayBufferView(response)){
|
|
447
450
|
try{return new TextDecoder('utf-8',{fatal:true}).decode(response);}
|
|
448
451
|
catch{fail('Document response is not valid UTF-8 text.','STATIC_DOCUMENT_INVALID_TEXT');}
|
|
449
452
|
}
|
|
450
|
-
if(!response
|
|
453
|
+
if(!response||!is.object(response)) fail('fetchImpl returned an invalid response.','STATIC_DOCUMENT_INVALID_RESPONSE');
|
|
451
454
|
if('ok' in response&&!response.ok){
|
|
452
455
|
fail(`Document request failed (${Number(response.status)||0}).`,'STATIC_DOCUMENT_HTTP_ERROR');
|
|
453
456
|
}
|
|
454
|
-
if(
|
|
457
|
+
if(is.function(response.arrayBuffer)){
|
|
455
458
|
return responseText(await response.arrayBuffer());
|
|
456
459
|
}
|
|
457
|
-
if(
|
|
460
|
+
if(is.function(response.text))return response.text();
|
|
458
461
|
if(response.body?.getReader){
|
|
459
462
|
const reader=response.body.getReader();
|
|
460
463
|
const decoder=new TextDecoder('utf-8',{fatal:true});
|
|
@@ -488,10 +491,10 @@ function abortError(message='The document request was aborted.'){
|
|
|
488
491
|
|
|
489
492
|
function signalLike(value){
|
|
490
493
|
return value===undefined||value===null||(
|
|
491
|
-
|
|
492
|
-
&&
|
|
493
|
-
&&
|
|
494
|
-
&&
|
|
494
|
+
is.object(value)
|
|
495
|
+
&&is.boolean(value.aborted)
|
|
496
|
+
&&is.function(value.addEventListener)
|
|
497
|
+
&&is.function(value.removeEventListener)
|
|
495
498
|
);
|
|
496
499
|
}
|
|
497
500
|
|
|
@@ -531,7 +534,7 @@ function timedOperation(operation,{milliseconds,signal}){
|
|
|
531
534
|
function hydrationOptions(input){
|
|
532
535
|
if(!isPlainRecord(input)) fail('Hydration options must be a plain object.','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
533
536
|
assertKnownKeys(input,new Set(['bypassCache','signal']),'Hydration options','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
534
|
-
if(input.bypassCache!==undefined
|
|
537
|
+
if(input.bypassCache!==undefined&&!is.boolean(input.bypassCache)) fail('bypassCache must be a boolean.','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
535
538
|
if(!signalLike(input.signal)) fail('signal must be an AbortSignal.','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
536
539
|
return {bypassCache:Boolean(input.bypassCache),signal:input.signal??null};
|
|
537
540
|
}
|
|
@@ -545,7 +548,7 @@ function normalizedError(error){
|
|
|
545
548
|
}
|
|
546
549
|
}catch{}
|
|
547
550
|
return completeValue({
|
|
548
|
-
code:
|
|
551
|
+
code:is.string(error?.code)?error.code:'STATIC_DOCUMENT_ERROR',
|
|
549
552
|
message,
|
|
550
553
|
});
|
|
551
554
|
}
|
|
@@ -615,7 +618,7 @@ export default class StaticDocumentCatalog{
|
|
|
615
618
|
}
|
|
616
619
|
|
|
617
620
|
get(id){
|
|
618
|
-
if(
|
|
621
|
+
if(!is.string(id)||!ID_PATTERN.test(id)) fail('Document id is invalid.','STATIC_DOCUMENT_INVALID_ID');
|
|
619
622
|
return this.#recordsById.get(id)??null;
|
|
620
623
|
}
|
|
621
624
|
|
|
@@ -685,7 +688,7 @@ export default class StaticDocumentCatalog{
|
|
|
685
688
|
||entry.schemaVersion!==CATALOG_SCHEMA_VERSION
|
|
686
689
|
||entry.catalogVersion!==this.version
|
|
687
690
|
||entry.documentId!==record.id
|
|
688
|
-
|
|
691
|
+
||!is.string(entry.text)
|
|
689
692
|
) fail('Cached document metadata is invalid.','STATIC_DOCUMENT_CACHE_INVALID');
|
|
690
693
|
return this.#retainHydration(record,entry.text,this.#resolve(record).href);
|
|
691
694
|
}catch(error){
|
|
@@ -745,7 +748,7 @@ export default class StaticDocumentCatalog{
|
|
|
745
748
|
async buildContext(query,options={}){
|
|
746
749
|
if(!isPlainRecord(options)) fail('Context options must be a plain object.','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
747
750
|
if(!signalLike(options.signal)) fail('signal must be an AbortSignal.','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
748
|
-
if(options.bodySearch!==undefined
|
|
751
|
+
if(options.bodySearch!==undefined&&!is.boolean(options.bodySearch)) fail('bodySearch must be a boolean.','STATIC_DOCUMENT_INVALID_OPTIONS');
|
|
749
752
|
const queryValue=queryText(query);
|
|
750
753
|
const bodySearch=Boolean(options.bodySearch)&&Boolean(queryValue);
|
|
751
754
|
const indexedMatches=this.#lexicalSearch.rank(queryValue);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
export default class SystemAppearance{
|
|
2
5
|
constructor(api=globalThis.Arcane?.appearance||null){ this.api=api; }
|
|
3
6
|
|
|
4
|
-
available(){ return Boolean(this.api&&
|
|
7
|
+
available(){ return Boolean(this.api&&is.function(this.api.apply)); }
|
|
5
8
|
|
|
6
9
|
async current(){
|
|
7
|
-
if(!this.api
|
|
10
|
+
if(!this.api||!is.function(this.api.current)) return {supported:false,platform:'browser'};
|
|
8
11
|
return this.api.current();
|
|
9
12
|
}
|
|
10
13
|
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
const TOOL_PATTERN=/^[a-z][a-z0-9-]{0,63}$/;
|
|
2
5
|
|
|
3
6
|
function quoteArgument(value){
|
|
@@ -11,7 +14,7 @@ export default class SystemToolRegistry{
|
|
|
11
14
|
register(definition={}){
|
|
12
15
|
const id=String(definition.id||'').trim().toLowerCase();
|
|
13
16
|
if(!TOOL_PATTERN.test(id)||this.tools.has(id))throw new TypeError(`Duplicate or invalid system tool: ${id}`);
|
|
14
|
-
if(
|
|
17
|
+
if(!is.function(definition.command)&&!is.string(definition.command))throw new TypeError(`System tool ${id} requires a command builder.`);
|
|
15
18
|
const record=Object.freeze({id,label:String(definition.label||id),description:String(definition.description||''),usage:String(definition.usage||id),command:definition.command});
|
|
16
19
|
this.tools.set(id,record);return record;
|
|
17
20
|
}
|
|
@@ -19,8 +22,8 @@ export default class SystemToolRegistry{
|
|
|
19
22
|
get(id){return this.tools.get(String(id||'').toLowerCase())||null;}
|
|
20
23
|
build(id,args=[]){
|
|
21
24
|
const tool=this.get(id);if(!tool)throw new RangeError(`Unknown system tool: ${id}`);
|
|
22
|
-
const command=
|
|
23
|
-
if(
|
|
25
|
+
const command=is.function(tool.command)?tool.command(Array.from(args)):tool.command;
|
|
26
|
+
if(!is.string(command)||!command.trim())throw new TypeError(`System tool ${id} produced an empty command.`);
|
|
24
27
|
return command.trim();
|
|
25
28
|
}
|
|
26
29
|
}
|