arcane-os 0.86.1 → 0.88.0
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 +34 -0
- package/README.md +1 -1
- package/browser-runtime/core/contracts.mjs +1 -0
- package/browser-runtime/document-acquisition.mjs +112 -0
- package/docs/reference/README.md +1 -0
- package/docs/reference/document-acquisition.md +159 -0
- package/docs/reference/inventory/package-api.json +30 -2
- package/docs/reference/local-ai.md +17 -0
- package/docs/reference/model-observation.md +7 -0
- package/docs/reference/protocols.md +15 -0
- package/docs/reference/runtime-components.md +6 -2
- package/docs/reference/runtime-modules.md +35 -4
- package/docs/reference/sdk-api.md +62 -0
- package/package.json +4 -2
- package/runtime/arcane/components/theme-switcher.html +10 -5
- package/runtime/arcane/modules/AI.js +306 -120
- package/runtime/arcane/modules/AIRuntimeState.js +5 -2
- package/src/core/services/document-acquisition.mjs +221 -0
- package/src/import-map.mjs +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.88.0",
|
|
4
4
|
"description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"./entities/*": "./runtime/arcane/entities/*",
|
|
20
20
|
"./logging": "./browser-runtime/logging.mjs",
|
|
21
21
|
"./browser-device": "./browser-runtime/browser-device.mjs",
|
|
22
|
+
"./document-acquisition": "./browser-runtime/document-acquisition.mjs",
|
|
22
23
|
"./pwa": "./browser-runtime/pwa.mjs",
|
|
23
24
|
"./preference-store": "./runtime/arcane/modules/PreferenceStore.js",
|
|
24
25
|
"./ai": "./runtime/arcane/modules/AI.js",
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"./core/repositories": "./src/core/repositories.mjs",
|
|
70
71
|
"./core/packaged-web": "./src/core/services/packaged-web.mjs",
|
|
71
72
|
"./core/preferences": "./src/core/services/preferences.mjs",
|
|
73
|
+
"./core/document-acquisition": "./src/core/services/document-acquisition.mjs",
|
|
72
74
|
"./core/diarization": "./src/diarization/service.mjs",
|
|
73
75
|
"./core/local-ai": "./src/core/services/local-ai.mjs",
|
|
74
76
|
"./core/execution-devices": "./src/core/services/execution-devices.mjs",
|
|
@@ -128,7 +130,7 @@
|
|
|
128
130
|
"test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
|
|
129
131
|
"test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
|
|
130
132
|
"test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/conversation-closing-report.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/native-windows-icon.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
|
|
131
|
-
"test:functional": "node ./bin/arcane-test.mjs test/ai-ollama-readiness.test.mjs test/core-ollama-lifecycle.test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-decisions.test.mjs test/browser-kokoro-complete-input.test.mjs test/browser-speech-providers.test.mjs test/browser-speech-artifacts.test.mjs test/browser-speech-resource-bridge.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/codex-app-server.test.mjs test/mcp-stdio-server.test.mjs test/core-classic-source.test.mjs test/core-client.test.mjs test/core-client-installation.test.mjs test/core-ai-installation.test.mjs test/core-llama-lifecycle.test.mjs test/core-development.test.mjs test/core-host.test.mjs test/core-shared-host.test.mjs test/core-model-assets.test.mjs test/core-native-services.test.mjs test/core-packaged-web.test.mjs test/core-runtime.test.mjs test/core-stdio.test.mjs test/dbopfs-committed-events.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-model-resources.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/execution-devices.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/local-image-editing.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/model-resource-bridge.test.mjs test/native-decisions.test.mjs test/native-onnx.test.mjs test/native-module-packaging.test.mjs test/native-module-resource-references.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/websocket-client-import-map.test.mjs test/websocket.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
133
|
+
"test:functional": "node ./bin/arcane-test.mjs test/ai-ollama-readiness.test.mjs test/core-ollama-lifecycle.test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-decisions.test.mjs test/browser-kokoro-complete-input.test.mjs test/browser-speech-providers.test.mjs test/browser-speech-artifacts.test.mjs test/browser-speech-resource-bridge.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/codex-app-server.test.mjs test/mcp-stdio-server.test.mjs test/core-classic-source.test.mjs test/core-client.test.mjs test/core-client-installation.test.mjs test/core-ai-installation.test.mjs test/core-llama-lifecycle.test.mjs test/core-development.test.mjs test/core-host.test.mjs test/core-shared-host.test.mjs test/core-model-assets.test.mjs test/core-native-services.test.mjs test/core-packaged-web.test.mjs test/core-runtime.test.mjs test/core-stdio.test.mjs test/dbopfs-committed-events.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-model-resources.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/document-acquisition.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/execution-devices.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/local-image-editing.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/model-resource-bridge.test.mjs test/native-decisions.test.mjs test/native-onnx.test.mjs test/native-module-packaging.test.mjs test/native-module-resource-references.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/websocket-client-import-map.test.mjs test/websocket.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
132
134
|
"test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/root-app-layout.test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-macos-provider.test.mjs test/native-plan.test.mjs test/native-portable-provider.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
|
|
133
135
|
"test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/html-import-packaged-script.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/ai-audio-output.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
|
|
134
136
|
"check": "node tools/check-source.mjs && npm test",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
overflow-wrap:anywhere;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
button:hover:not(:disabled):not([aria-pressed="true"]) {
|
|
41
|
+
button:hover:not(:disabled):not([aria-disabled="true"]):not([aria-pressed="true"]) {
|
|
42
42
|
background:color-mix(in srgb,currentColor 14%,transparent);
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
box-shadow:inset 0 0 0 .1rem currentColor;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
button:disabled
|
|
51
|
+
button:disabled,
|
|
52
|
+
button[aria-disabled="true"] {
|
|
52
53
|
cursor:not-allowed;
|
|
53
54
|
opacity:.55;
|
|
54
55
|
}
|
|
@@ -88,7 +89,7 @@
|
|
|
88
89
|
host.refresh=refresh;
|
|
89
90
|
group.addEventListener('click',async function selectScheme(event){
|
|
90
91
|
const button=event.target.closest('[data-scheme]');
|
|
91
|
-
if(!button||button.disabled) return;
|
|
92
|
+
if(!button||button.disabled||busy) return;
|
|
92
93
|
setBusy(true);
|
|
93
94
|
try{
|
|
94
95
|
const state=button.dataset.scheme==='custom'?await manager.activateCustom():await manager.setScheme(button.dataset.scheme);
|
|
@@ -134,10 +135,14 @@
|
|
|
134
135
|
}
|
|
135
136
|
function setBusy(value){
|
|
136
137
|
busy=Boolean(value);
|
|
137
|
-
|
|
138
|
+
// Keep the focused choice in the keyboard flow while its operation
|
|
139
|
+
// settles. The click owner rejects repeat activation without moving focus.
|
|
140
|
+
for(const button of buttons) button.setAttribute('aria-disabled',String(busy||button.disabled));
|
|
138
141
|
}
|
|
139
142
|
function setCustomAvailable(value){
|
|
140
|
-
buttons.find(function customScheme(button){return button.dataset.scheme==='custom';})
|
|
143
|
+
const button=buttons.find(function customScheme(button){return button.dataset.scheme==='custom';});
|
|
144
|
+
button.disabled=!value;
|
|
145
|
+
button.setAttribute('aria-disabled',String(busy||button.disabled));
|
|
141
146
|
}
|
|
142
147
|
async function refresh(){const state=await manager.load();setMode(state.mode);setCustomAvailable(Boolean(state.theme));return state;}
|
|
143
148
|
|
|
@@ -759,6 +759,19 @@ function isAbortSignal(value){
|
|
|
759
759
|
&&is.function(value.removeEventListener);
|
|
760
760
|
}
|
|
761
761
|
|
|
762
|
+
function normalizeAISelectionOptions({signal=null,startLanguageModel=true}={}){
|
|
763
|
+
if(signal!==null&&!isAbortSignal(signal)){
|
|
764
|
+
throw new TypeError('AI selection signal must be an AbortSignal.');
|
|
765
|
+
}
|
|
766
|
+
if(!is.boolean(startLanguageModel)){
|
|
767
|
+
throw new TypeError('AI selection startLanguageModel must be a boolean.');
|
|
768
|
+
}
|
|
769
|
+
if(signal?.aborted){
|
|
770
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
771
|
+
}
|
|
772
|
+
return {signal,startLanguageModel};
|
|
773
|
+
}
|
|
774
|
+
|
|
762
775
|
function closedRecord(value,keys,required,label){
|
|
763
776
|
if(!value
|
|
764
777
|
||!is.object(value)
|
|
@@ -1173,6 +1186,8 @@ class AI {
|
|
|
1173
1186
|
#browserSpeechTransition=Promise.resolve();
|
|
1174
1187
|
#builtInLLMProviders=new Map();
|
|
1175
1188
|
#builtInLLMReadiness=Promise.resolve(null);
|
|
1189
|
+
#providerSelectionGeneration=0;
|
|
1190
|
+
#providerSelectionOperation=null;
|
|
1176
1191
|
#builtInSpeechProviders=new Map();
|
|
1177
1192
|
#configuredSpeechProviders=new Map();
|
|
1178
1193
|
#builtInSpeechReadiness=Promise.resolve(null);
|
|
@@ -2146,7 +2161,8 @@ class AI {
|
|
|
2146
2161
|
});
|
|
2147
2162
|
}
|
|
2148
2163
|
|
|
2149
|
-
#ensureBuiltInLLMProvider(providerId){
|
|
2164
|
+
#ensureBuiltInLLMProvider(providerId,operation=null){
|
|
2165
|
+
if(operation)this.#assertProviderSelection(operation);
|
|
2150
2166
|
if(providerId!=='TWIN'&&providerId!=='OLLAMA'){
|
|
2151
2167
|
return false;
|
|
2152
2168
|
}
|
|
@@ -2154,15 +2170,34 @@ class AI {
|
|
|
2154
2170
|
return false;
|
|
2155
2171
|
}
|
|
2156
2172
|
const provider=this.#createBuiltInLLMProvider(providerId);
|
|
2157
|
-
const
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2173
|
+
const runtime=this;
|
|
2174
|
+
let unregister=null;
|
|
2175
|
+
const record={
|
|
2176
|
+
provider,
|
|
2177
|
+
unregister:function unregisterBuiltInLLMProvider(){
|
|
2178
|
+
if(unregister)return unregister();
|
|
2179
|
+
if(!runtime.#providerRuntime.ownsProvider('llm',provider))return false;
|
|
2180
|
+
return runtime.#providerRuntime.unregister('llm',providerId);
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
// Registration can publish a pending route synchronously; its owner must
|
|
2184
|
+
// already be visible to a selection accepted by that callback.
|
|
2185
|
+
this.#builtInLLMProviders.set(providerId,record);
|
|
2186
|
+
try{
|
|
2187
|
+
unregister=this.#providerRuntime.register(provider);
|
|
2188
|
+
}catch(error){
|
|
2189
|
+
if(!this.#providerRuntime.ownsProvider('llm',provider)
|
|
2190
|
+
&&this.#builtInLLMProviders.get(providerId)===record){
|
|
2191
|
+
this.#builtInLLMProviders.delete(providerId);
|
|
2192
|
+
}
|
|
2193
|
+
throw error;
|
|
2194
|
+
}
|
|
2195
|
+
if(operation)this.#assertProviderSelection(operation);
|
|
2162
2196
|
return true;
|
|
2163
2197
|
}
|
|
2164
2198
|
|
|
2165
|
-
#ensureBuiltInSpeechProvider(role,providerId){
|
|
2199
|
+
#ensureBuiltInSpeechProvider(role,providerId,operation=null){
|
|
2200
|
+
if(operation)this.#assertProviderSelection(operation);
|
|
2166
2201
|
if(!['stt','tts'].includes(role)||providerId!=='LOCAL_SPEACH'){
|
|
2167
2202
|
return false;
|
|
2168
2203
|
}
|
|
@@ -2170,33 +2205,54 @@ class AI {
|
|
|
2170
2205
|
return false;
|
|
2171
2206
|
}
|
|
2172
2207
|
const provider=this.#createBuiltInSpeechProvider(role,providerId);
|
|
2173
|
-
const
|
|
2174
|
-
this.#
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2208
|
+
const runtime=this;
|
|
2209
|
+
const key=this.#builtInSpeechProviderKey(role,providerId);
|
|
2210
|
+
let unregister=null;
|
|
2211
|
+
const record={
|
|
2212
|
+
role,providerId,provider,
|
|
2213
|
+
unregister:function unregisterBuiltInSpeechProvider(){
|
|
2214
|
+
if(unregister)return unregister();
|
|
2215
|
+
if(!runtime.#providerRuntime.ownsProvider(role,provider))return false;
|
|
2216
|
+
return runtime.#providerRuntime.unregister(role,providerId);
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
this.#builtInSpeechProviders.set(key,record);
|
|
2220
|
+
try{
|
|
2221
|
+
unregister=this.#providerRuntime.register(provider);
|
|
2222
|
+
}catch(error){
|
|
2223
|
+
if(!this.#providerRuntime.ownsProvider(role,provider)
|
|
2224
|
+
&&this.#builtInSpeechProviders.get(key)===record){
|
|
2225
|
+
this.#builtInSpeechProviders.delete(key);
|
|
2226
|
+
}
|
|
2227
|
+
throw error;
|
|
2228
|
+
}
|
|
2229
|
+
if(operation)this.#assertProviderSelection(operation);
|
|
2178
2230
|
return true;
|
|
2179
2231
|
}
|
|
2180
2232
|
|
|
2181
|
-
#releaseInactiveBuiltInLLMProviders(activeProviderId){
|
|
2233
|
+
#releaseInactiveBuiltInLLMProviders(activeProviderId,operation=null){
|
|
2182
2234
|
for(const [providerId,record] of this.#builtInLLMProviders){
|
|
2235
|
+
if(operation)this.#assertProviderSelection(operation);
|
|
2183
2236
|
if(providerId===activeProviderId){
|
|
2184
2237
|
continue;
|
|
2185
2238
|
}
|
|
2186
|
-
if(record.unregister()){
|
|
2239
|
+
if(record.unregister()&&this.#builtInLLMProviders.get(providerId)===record){
|
|
2187
2240
|
this.#builtInLLMProviders.delete(providerId);
|
|
2188
2241
|
}
|
|
2189
2242
|
}
|
|
2190
2243
|
}
|
|
2191
2244
|
|
|
2192
|
-
#releaseInactiveBuiltInSpeechProviders(activeProviders){
|
|
2245
|
+
#releaseInactiveBuiltInSpeechProviders(activeProviders,operation=null){
|
|
2193
2246
|
for(const [key,record] of this.#builtInSpeechProviders){
|
|
2247
|
+
if(operation)this.#assertProviderSelection(operation);
|
|
2194
2248
|
if(activeProviders[record.role]===record.providerId){
|
|
2195
2249
|
continue;
|
|
2196
2250
|
}
|
|
2197
2251
|
if(record.unregister()){
|
|
2198
2252
|
record.provider.dispose();
|
|
2199
|
-
this.#builtInSpeechProviders.
|
|
2253
|
+
if(this.#builtInSpeechProviders.get(key)===record){
|
|
2254
|
+
this.#builtInSpeechProviders.delete(key);
|
|
2255
|
+
}
|
|
2200
2256
|
}
|
|
2201
2257
|
}
|
|
2202
2258
|
}
|
|
@@ -2246,7 +2302,85 @@ class AI {
|
|
|
2246
2302
|
return this.#builtInSpeechReadiness;
|
|
2247
2303
|
}
|
|
2248
2304
|
|
|
2249
|
-
#
|
|
2305
|
+
#beginProviderSelection({signal,startLanguageModel},deferSupersession=false){
|
|
2306
|
+
const previous=this.#providerSelectionOperation;
|
|
2307
|
+
const controller=new AbortController();
|
|
2308
|
+
const operation={
|
|
2309
|
+
generation:++this.#providerSelectionGeneration,
|
|
2310
|
+
controller,
|
|
2311
|
+
startLanguageModel,
|
|
2312
|
+
previous:deferSupersession?previous:null,
|
|
2313
|
+
restorePrevious:deferSupersession,
|
|
2314
|
+
committed:false,
|
|
2315
|
+
settled:false,
|
|
2316
|
+
detachSignal:function detachAISelectionSignal(){
|
|
2317
|
+
signal?.removeEventListener('abort',forwardAbort);
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
function forwardAbort(){
|
|
2321
|
+
controller.abort(signal.reason);
|
|
2322
|
+
}
|
|
2323
|
+
this.#providerSelectionOperation=operation;
|
|
2324
|
+
signal?.addEventListener('abort',forwardAbort,{once:true});
|
|
2325
|
+
if(signal?.aborted)forwardAbort();
|
|
2326
|
+
if(!deferSupersession)this.#supersedeProviderSelection(previous);
|
|
2327
|
+
return operation;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
#supersedeProviderSelection(previous){
|
|
2331
|
+
for(let operation=previous;operation;operation=operation.previous){
|
|
2332
|
+
operation.detachSignal();
|
|
2333
|
+
if(!operation.settled){
|
|
2334
|
+
operation.controller.abort(aiProviderError(
|
|
2335
|
+
'AI provider selection was superseded.',
|
|
2336
|
+
'ARCANE_AI_OPERATION_SUPERSEDED'
|
|
2337
|
+
));
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
#assertProviderSelection(operation,allowAborted=false){
|
|
2343
|
+
if(operation.generation!==this.#providerSelectionOperation?.generation){
|
|
2344
|
+
throw aiProviderError(
|
|
2345
|
+
'AI provider selection was superseded.',
|
|
2346
|
+
'ARCANE_AI_OPERATION_SUPERSEDED'
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
if(!allowAborted&&operation.controller.signal.aborted){
|
|
2350
|
+
throw normalizeAIRequestAbort(operation.controller.signal.reason);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
#commitProviderSelection(operation,tuple){
|
|
2355
|
+
// Configuration already committed its routes before notifying listeners.
|
|
2356
|
+
// Keep its tuple together even when that notification aborts this caller;
|
|
2357
|
+
// a newer owner alone prevents this continuation from applying the tuple.
|
|
2358
|
+
this.#assertProviderSelection(operation,true);
|
|
2359
|
+
this.#applyPreferenceTuple(tuple);
|
|
2360
|
+
operation.committed=true;
|
|
2361
|
+
const previous=operation.previous;
|
|
2362
|
+
operation.previous=null;
|
|
2363
|
+
this.#supersedeProviderSelection(previous);
|
|
2364
|
+
this.#assertProviderSelection(operation);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
#finishProviderSelection(operation){
|
|
2368
|
+
if(operation.restorePrevious&&!operation.committed
|
|
2369
|
+
&&this.#providerSelectionOperation===operation){
|
|
2370
|
+
this.#providerSelectionOperation=operation.previous;
|
|
2371
|
+
}
|
|
2372
|
+
operation.previous=null;
|
|
2373
|
+
operation.settled=true;
|
|
2374
|
+
operation.detachSignal();
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
#reconcileBuiltInLLMReadiness(operation=this.#providerSelectionOperation){
|
|
2378
|
+
if(operation&&(
|
|
2379
|
+
operation!==this.#providerSelectionOperation
|
|
2380
|
+
||!operation.committed
|
|
2381
|
+
)){
|
|
2382
|
+
return Promise.resolve(this.#providerRuntime.status('llm'));
|
|
2383
|
+
}
|
|
2250
2384
|
const selection=this.#builtInLLMSelection(false);
|
|
2251
2385
|
if(!selection){
|
|
2252
2386
|
return Promise.resolve(this.#providerRuntime.status('llm'));
|
|
@@ -2259,10 +2393,14 @@ class AI {
|
|
|
2259
2393
|
return this.#providerRuntime.unload('llm');
|
|
2260
2394
|
}
|
|
2261
2395
|
if(this.#builtInLLMCapability(selection.providerId)){
|
|
2262
|
-
if(
|
|
2396
|
+
if(operation?.startLanguageModel===false
|
|
2397
|
+
||operation?.controller.signal.aborted
|
|
2398
|
+
||(status.state==='ready'&&status.loaded===true)){
|
|
2263
2399
|
return Promise.resolve(status);
|
|
2264
2400
|
}
|
|
2265
|
-
return this.#providerRuntime.load('llm'
|
|
2401
|
+
return this.#providerRuntime.load('llm',{
|
|
2402
|
+
signal:operation?.controller.signal??null
|
|
2403
|
+
});
|
|
2266
2404
|
}
|
|
2267
2405
|
if(status.loaded===true
|
|
2268
2406
|
||status.busy===true
|
|
@@ -2591,12 +2729,15 @@ class AI {
|
|
|
2591
2729
|
}
|
|
2592
2730
|
}
|
|
2593
2731
|
|
|
2594
|
-
async #unloadProviderRolesForTransition(){
|
|
2595
|
-
const
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2732
|
+
async #unloadProviderRolesForTransition(operation){
|
|
2733
|
+
const runtime=this;
|
|
2734
|
+
const settlements=await Promise.allSettled(
|
|
2735
|
+
['llm','stt','tts'].map(async function unloadPreviousAIProviderRole(role){
|
|
2736
|
+
runtime.#assertProviderSelection(operation);
|
|
2737
|
+
// Accepted cleanup owns completion independently of page cancellation.
|
|
2738
|
+
return runtime.#providerRuntime.unload(role);
|
|
2739
|
+
})
|
|
2740
|
+
);
|
|
2600
2741
|
const failure=settlements.find(function findAITransitionCleanupFailure(result){
|
|
2601
2742
|
return result.status==='rejected';
|
|
2602
2743
|
});
|
|
@@ -2643,7 +2784,8 @@ class AI {
|
|
|
2643
2784
|
ttsService,
|
|
2644
2785
|
model,
|
|
2645
2786
|
modelTTS,
|
|
2646
|
-
modelSTT
|
|
2787
|
+
modelSTT,
|
|
2788
|
+
options
|
|
2647
2789
|
) {
|
|
2648
2790
|
if (
|
|
2649
2791
|
!(
|
|
@@ -2667,59 +2809,83 @@ class AI {
|
|
|
2667
2809
|
]);
|
|
2668
2810
|
this.#assertValidProviderTuple(tuple);
|
|
2669
2811
|
this.#assertSynchronousBrowserSpeechSupersession('AI.setAI');
|
|
2670
|
-
|
|
2671
|
-
this.#
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
this.#
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2812
|
+
const normalized=normalizeAISelectionOptions(options);
|
|
2813
|
+
const operation=this.#beginProviderSelection(normalized,true);
|
|
2814
|
+
try{
|
|
2815
|
+
this.#assertProviderSelection(operation);
|
|
2816
|
+
this.#ensureBuiltInLLMProvider(tuple[0],operation);
|
|
2817
|
+
this.#ensureBuiltInSpeechProvider('stt',tuple[1],operation);
|
|
2818
|
+
this.#ensureBuiltInSpeechProvider('tts',tuple[2],operation);
|
|
2819
|
+
this.#providerRuntime.configure(this.#routesFromPreferenceTuple(tuple));
|
|
2820
|
+
this.#commitProviderSelection(operation,tuple);
|
|
2821
|
+
this.#invalidateSpeechControl();
|
|
2822
|
+
this.#assertProviderSelection(operation);
|
|
2823
|
+
this.#releaseInactiveBuiltInLLMProviders(tuple[0],operation);
|
|
2824
|
+
this.#assertProviderSelection(operation);
|
|
2825
|
+
this.#releaseInactiveBuiltInSpeechProviders({
|
|
2826
|
+
stt:tuple[1],
|
|
2827
|
+
tts:tuple[2]
|
|
2828
|
+
},operation);
|
|
2829
|
+
this.#assertProviderSelection(operation);
|
|
2830
|
+
const runtime=this;
|
|
2831
|
+
this.#retainBuiltInLLMReadiness(
|
|
2832
|
+
this.#reconcileBuiltInLLMReadiness(operation).finally(
|
|
2833
|
+
function settleAISelectionReadiness(){
|
|
2834
|
+
runtime.#finishProviderSelection(operation);
|
|
2835
|
+
}
|
|
2836
|
+
)
|
|
2837
|
+
);
|
|
2838
|
+
this.#assertProviderSelection(operation);
|
|
2839
|
+
this.#retainBuiltInSpeechReadiness(
|
|
2840
|
+
this.#reconcileBuiltInSpeechReadiness()
|
|
2841
|
+
);
|
|
2842
|
+
return true;
|
|
2843
|
+
}catch(error){
|
|
2844
|
+
this.#finishProviderSelection(operation);
|
|
2845
|
+
throw error;
|
|
2846
|
+
}
|
|
2688
2847
|
}
|
|
2689
2848
|
|
|
2690
|
-
configureProviders(selections){
|
|
2849
|
+
configureProviders(selections,options){
|
|
2691
2850
|
const prepared=this.#providerRuntime.validateConfiguration(selections);
|
|
2692
2851
|
this.#assertDeviceSpeechConfiguration(prepared);
|
|
2693
2852
|
this.#assertSynchronousBrowserSpeechSupersession('AI.configureProviders');
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
prepared.stt.default?.providerId
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2853
|
+
const normalized=normalizeAISelectionOptions(options);
|
|
2854
|
+
const operation=this.#beginProviderSelection(normalized,true);
|
|
2855
|
+
try{
|
|
2856
|
+
this.#assertProviderSelection(operation);
|
|
2857
|
+
this.#ensureBuiltInLLMProvider(prepared.llm.default?.providerId,operation);
|
|
2858
|
+
this.#ensureBuiltInSpeechProvider('stt',prepared.stt.default?.providerId,operation);
|
|
2859
|
+
this.#ensureBuiltInSpeechProvider('tts',prepared.tts.default?.providerId,operation);
|
|
2860
|
+
this.#assertRegisteredBuiltInRoutes(prepared);
|
|
2861
|
+
const configured=this.#providerRuntime.configure(prepared);
|
|
2862
|
+
this.#commitProviderSelection(operation,this.#tupleFromProviderRoutes(configured));
|
|
2863
|
+
this.#invalidateSpeechControl();
|
|
2864
|
+
this.#assertProviderSelection(operation);
|
|
2865
|
+
this.#releaseInactiveBuiltInLLMProviders(configured.llm.default?.providerId,operation);
|
|
2866
|
+
this.#assertProviderSelection(operation);
|
|
2867
|
+
this.#releaseInactiveBuiltInSpeechProviders({
|
|
2868
|
+
stt:configured.stt.default?.providerId,
|
|
2869
|
+
tts:configured.tts.default?.providerId
|
|
2870
|
+
},operation);
|
|
2871
|
+
this.#assertProviderSelection(operation);
|
|
2872
|
+
const runtime=this;
|
|
2873
|
+
this.#retainBuiltInLLMReadiness(
|
|
2874
|
+
this.#reconcileBuiltInLLMReadiness(operation).finally(
|
|
2875
|
+
function settleAIProviderConfigurationReadiness(){
|
|
2876
|
+
runtime.#finishProviderSelection(operation);
|
|
2877
|
+
}
|
|
2878
|
+
)
|
|
2879
|
+
);
|
|
2880
|
+
this.#assertProviderSelection(operation);
|
|
2881
|
+
this.#retainBuiltInSpeechReadiness(
|
|
2882
|
+
this.#reconcileBuiltInSpeechReadiness()
|
|
2883
|
+
);
|
|
2884
|
+
return configured;
|
|
2885
|
+
}catch(error){
|
|
2886
|
+
this.#finishProviderSelection(operation);
|
|
2887
|
+
throw error;
|
|
2888
|
+
}
|
|
2723
2889
|
}
|
|
2724
2890
|
|
|
2725
2891
|
configureSpeechProviders(selections){
|
|
@@ -2762,7 +2928,8 @@ class AI {
|
|
|
2762
2928
|
ttsService,
|
|
2763
2929
|
model,
|
|
2764
2930
|
modelTTS,
|
|
2765
|
-
modelSTT
|
|
2931
|
+
modelSTT,
|
|
2932
|
+
options
|
|
2766
2933
|
){
|
|
2767
2934
|
const tuple=this.#nextPreferenceTuple([
|
|
2768
2935
|
llmService,
|
|
@@ -2773,54 +2940,70 @@ class AI {
|
|
|
2773
2940
|
modelSTT
|
|
2774
2941
|
]);
|
|
2775
2942
|
this.#assertValidProviderTuple(tuple);
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2943
|
+
const operation=this.#beginProviderSelection(normalizeAISelectionOptions(options));
|
|
2944
|
+
try{
|
|
2945
|
+
this.#assertProviderSelection(operation);
|
|
2946
|
+
await this.#supersedeBrowserSpeechForRouteChange(operation);
|
|
2947
|
+
this.#assertProviderSelection(operation);
|
|
2948
|
+
this.#invalidateSpeechControl();
|
|
2949
|
+
await this.#unloadProviderRolesForTransition(operation);
|
|
2950
|
+
this.#assertProviderSelection(operation);
|
|
2951
|
+
this.#ensureBuiltInLLMProvider(tuple[0],operation);
|
|
2952
|
+
this.#ensureBuiltInSpeechProvider('stt',tuple[1],operation);
|
|
2953
|
+
this.#ensureBuiltInSpeechProvider('tts',tuple[2],operation);
|
|
2954
|
+
this.#assertProviderSelection(operation);
|
|
2955
|
+
this.#providerRuntime.configure(this.#routesFromPreferenceTuple(tuple));
|
|
2956
|
+
this.#commitProviderSelection(operation,tuple);
|
|
2957
|
+
this.#releaseInactiveBuiltInLLMProviders(tuple[0],operation);
|
|
2958
|
+
this.#assertProviderSelection(operation);
|
|
2959
|
+
this.#releaseInactiveBuiltInSpeechProviders({
|
|
2960
|
+
stt:tuple[1],
|
|
2961
|
+
tts:tuple[2]
|
|
2962
|
+
},operation);
|
|
2963
|
+
this.#assertProviderSelection(operation);
|
|
2964
|
+
await this.#reconcileBuiltInLLMReadiness(operation);
|
|
2965
|
+
this.#assertProviderSelection(operation);
|
|
2966
|
+
await this.#reconcileBuiltInSpeechReadiness();
|
|
2967
|
+
this.#assertProviderSelection(operation);
|
|
2968
|
+
return this.#providerRuntime.status();
|
|
2969
|
+
}finally{
|
|
2970
|
+
this.#finishProviderSelection(operation);
|
|
2971
|
+
}
|
|
2792
2972
|
}
|
|
2793
2973
|
|
|
2794
|
-
async transitionProviders(selections){
|
|
2974
|
+
async transitionProviders(selections,options){
|
|
2795
2975
|
const prepared=this.#providerRuntime.validateConfiguration(selections);
|
|
2796
2976
|
this.#assertDeviceSpeechConfiguration(prepared);
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
'
|
|
2807
|
-
prepared.tts.default?.providerId
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2977
|
+
const operation=this.#beginProviderSelection(normalizeAISelectionOptions(options));
|
|
2978
|
+
try{
|
|
2979
|
+
this.#assertProviderSelection(operation);
|
|
2980
|
+
await this.#supersedeBrowserSpeechForRouteChange(operation);
|
|
2981
|
+
this.#assertProviderSelection(operation);
|
|
2982
|
+
this.#invalidateSpeechControl();
|
|
2983
|
+
await this.#unloadProviderRolesForTransition(operation);
|
|
2984
|
+
this.#assertProviderSelection(operation);
|
|
2985
|
+
this.#ensureBuiltInLLMProvider(prepared.llm.default?.providerId,operation);
|
|
2986
|
+
this.#ensureBuiltInSpeechProvider('stt',prepared.stt.default?.providerId,operation);
|
|
2987
|
+
this.#ensureBuiltInSpeechProvider('tts',prepared.tts.default?.providerId,operation);
|
|
2988
|
+
this.#assertRegisteredBuiltInRoutes(prepared);
|
|
2989
|
+
this.#assertProviderSelection(operation);
|
|
2990
|
+
const configured=this.#providerRuntime.configure(prepared);
|
|
2991
|
+
this.#commitProviderSelection(operation,this.#tupleFromProviderRoutes(configured));
|
|
2992
|
+
this.#releaseInactiveBuiltInLLMProviders(configured.llm.default?.providerId,operation);
|
|
2993
|
+
this.#assertProviderSelection(operation);
|
|
2994
|
+
this.#releaseInactiveBuiltInSpeechProviders({
|
|
2995
|
+
stt:configured.stt.default?.providerId,
|
|
2996
|
+
tts:configured.tts.default?.providerId
|
|
2997
|
+
},operation);
|
|
2998
|
+
this.#assertProviderSelection(operation);
|
|
2999
|
+
await this.#reconcileBuiltInLLMReadiness(operation);
|
|
3000
|
+
this.#assertProviderSelection(operation);
|
|
3001
|
+
await this.#reconcileBuiltInSpeechReadiness();
|
|
3002
|
+
this.#assertProviderSelection(operation);
|
|
3003
|
+
return configured;
|
|
3004
|
+
}finally{
|
|
3005
|
+
this.#finishProviderSelection(operation);
|
|
3006
|
+
}
|
|
2824
3007
|
}
|
|
2825
3008
|
|
|
2826
3009
|
async transitionSpeechProviders(selections){
|
|
@@ -3061,10 +3244,13 @@ class AI {
|
|
|
3061
3244
|
);
|
|
3062
3245
|
}
|
|
3063
3246
|
|
|
3064
|
-
async #supersedeBrowserSpeechForRouteChange(){
|
|
3247
|
+
async #supersedeBrowserSpeechForRouteChange(operation=null){
|
|
3065
3248
|
for(const role of [...this.#configuredSpeechProviders.keys()]){
|
|
3249
|
+
if(operation)this.#assertProviderSelection(operation);
|
|
3066
3250
|
await this.configureSpeechProvider(role,null);
|
|
3251
|
+
if(operation)this.#assertProviderSelection(operation);
|
|
3067
3252
|
}
|
|
3253
|
+
if(operation)this.#assertProviderSelection(operation);
|
|
3068
3254
|
if(!this.#browserSpeechConfigurationRecord
|
|
3069
3255
|
&&this.#browserSpeechRetiredRecords.size===0){
|
|
3070
3256
|
return false;
|
|
@@ -304,8 +304,11 @@ function copyRoleRecord(role, record) {
|
|
|
304
304
|
}
|
|
305
305
|
const pendingRecovery = ['recovering', 'error'].includes(record.state)
|
|
306
306
|
&& record.operationId !== null;
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
const pendingUnload = record.state === 'unloading'
|
|
308
|
+
&& record.loaded && record.operationId !== null;
|
|
309
|
+
if (record.busy && !pendingRecovery && !pendingUnload
|
|
310
|
+
&& (record.state !== 'ready' || !record.loaded)) {
|
|
311
|
+
fail('a busy role must be ready and loaded, retain its recovering/error operation, or retain its loaded unloading operation.');
|
|
309
312
|
}
|
|
310
313
|
if (record.state === 'error' && error === null) {
|
|
311
314
|
fail('an error role state must include error details.');
|