arcane-os 0.5.7 → 0.5.9
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 +25 -0
- package/README.md +39 -27
- package/browser-runtime/ai/browser-speech-artifacts.mjs +23 -1683
- package/browser-runtime/ai/browser-speech-providers.mjs +353 -143
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +32 -324
- package/browser-runtime/ai/speech-worker-client.mjs +51 -14
- package/browser-runtime/ai/speech-worker-runtime.mjs +45 -1072
- package/browser-runtime/dependencies/strong-type/package.json +22 -23
- package/browser-runtime/event-manager.mjs +27 -41
- package/package.json +2 -3
- package/runtime/arcane/components/chat.html +1 -1
- package/runtime/arcane/components/speech.html +8 -38
- package/runtime/arcane/components/voice-transcription.html +1 -1
- package/runtime/arcane/css/theme.css +1 -1
- package/runtime/arcane/entities/Chat.js +12 -7
- package/runtime/arcane/modules/AI.js +569 -402
- package/runtime/arcane/modules/AIPreferenceTuple.js +1 -1
- package/runtime/arcane/modules/AIProviderRuntime.js +129 -68
- package/runtime/arcane/modules/AIRuntimeState.js +2 -18
- package/runtime/arcane/modules/BrowserTestSuite.js +2 -5
- package/runtime/arcane/modules/CalculatorEngine.js +0 -4
- package/runtime/arcane/modules/ChatRecords.js +169 -1
- package/runtime/arcane/modules/CommunicationHub.js +0 -19
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +28 -25
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +4 -5
- package/runtime/arcane/modules/DocumentLexicalSearch.js +1 -1
- package/runtime/arcane/modules/Errors.js +6 -19
- package/runtime/arcane/modules/Mail.js +1 -2
- package/runtime/arcane/modules/MailTransport.mjs +1 -3
- package/runtime/arcane/modules/OllamaModelIdentifier.js +1 -1
- package/runtime/arcane/modules/PersistentAIChatSession.js +5 -5
- package/runtime/arcane/modules/ScreenCapture.js +2 -4
- package/runtime/arcane/modules/SpeechPlayback.js +0 -27
- package/runtime/arcane/modules/WaitForComponent.js +0 -1
- package/src/app-descriptor.mjs +1 -1
- package/src/doctor.mjs +1 -3
- package/src/event-manager.mjs +27 -41
- package/src/import-map.mjs +1 -1
- package/src/index.mjs +2 -9
- package/src/installed-sdk-runtime.mjs +1 -11
- package/src/mail-api.mjs +0 -1
- package/src/native-provider-loader.mjs +0 -9
- package/src/scaffold.mjs +9 -21
- package/src/toolchain.mjs +5 -28
- package/src/workspace-runtime.mjs +0 -4
- package/src/workspace.mjs +2 -23
|
@@ -16,11 +16,17 @@ import {normalizeOllamaModelIdentifier} from './OllamaModelIdentifier.js';
|
|
|
16
16
|
const completeValue=(value)=>value;
|
|
17
17
|
|
|
18
18
|
let credentials='include';
|
|
19
|
-
const
|
|
19
|
+
const DEFAULT_TTS_RESPONSE_FORMAT='opus';
|
|
20
20
|
const DEFAULT_TTS_SEGMENTATION={
|
|
21
21
|
punctuation:'sentence',
|
|
22
22
|
wordCadence:null
|
|
23
23
|
};
|
|
24
|
+
const DEFAULT_BROWSER_TTS_EXECUTION={
|
|
25
|
+
device:'auto',
|
|
26
|
+
maxConcurrentRequests:2
|
|
27
|
+
};
|
|
28
|
+
const BROWSER_TTS_EXECUTION_DEVICES=new Set(['auto','webgpu','wasm']);
|
|
29
|
+
const MAX_BROWSER_TTS_CONCURRENT_REQUESTS=4;
|
|
24
30
|
const TTS_PUNCTUATION_MODES=new Set(['sentence','any','none']);
|
|
25
31
|
// A complete punctuation run is a boundary unless the whole run consists of
|
|
26
32
|
// apostrophe, comma, or dash punctuation joining Unicode letters or numbers.
|
|
@@ -29,7 +35,8 @@ const TTS_ANY_PUNCTUATION=/(?<!\p{P})(?:(?<![\p{L}\p{N}])\p{P}+(?!\p{P})(?=[\s\S
|
|
|
29
35
|
const TTS_ANY_PUNCTUATION_AT_END=/(?<!\p{P})(?:(?<![\p{L}\p{N}])\p{P}+|\p{P}+(?![\p{L}\p{N}])|\p{P}*[^\P{P}'\u2019\uFF07,\u060C\u3001\uFF0C\p{Pd}]\p{P}*)(?!\p{P})/gu;
|
|
30
36
|
credentials='omit';
|
|
31
37
|
|
|
32
|
-
const
|
|
38
|
+
const BUILT_IN_AI_SERVICES=new Set(['TWIN','OLLAMA','LOCAL_SPEACH']);
|
|
39
|
+
const AI_REASONING_EFFORTS=new Set(['none','low','medium','high','max']);
|
|
33
40
|
export const AI_READY_EVENT='ai-ready';
|
|
34
41
|
const AI_TTS_FAILURE_EVENT='ai-tts-failure';
|
|
35
42
|
export const AI_INITIALIZATION_ERROR_CODES=completeValue({
|
|
@@ -128,7 +135,21 @@ function normalizeAIRequestAbort(error){
|
|
|
128
135
|
return normalized;
|
|
129
136
|
}
|
|
130
137
|
|
|
131
|
-
function
|
|
138
|
+
function normalizeAIReasoningEffort(value){
|
|
139
|
+
if(value===undefined||value===null||value===''){
|
|
140
|
+
return '';
|
|
141
|
+
}
|
|
142
|
+
if(typeof value!=='string'||!AI_REASONING_EFFORTS.has(value)){
|
|
143
|
+
const error=new TypeError(
|
|
144
|
+
'AI reasoningEffort must be none, low, medium, high, or max.'
|
|
145
|
+
);
|
|
146
|
+
error.code='AI_REASONING_EFFORT_INVALID';
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function aiProviderError(message,code,cause){
|
|
132
153
|
const error=cause===undefined
|
|
133
154
|
?new Error(message)
|
|
134
155
|
:new Error(message,{cause});
|
|
@@ -520,7 +541,7 @@ function projectAIStreamChunk(value){
|
|
|
520
541
|
return projectAIStreamData(value);
|
|
521
542
|
}
|
|
522
543
|
|
|
523
|
-
function
|
|
544
|
+
function createBuiltInAIStreamBridge(execute,sourceSignal){
|
|
524
545
|
const controller=new AbortController();
|
|
525
546
|
const queue=[];
|
|
526
547
|
const waiters=[];
|
|
@@ -528,34 +549,34 @@ function createLegacyAIStreamBridge(execute,sourceSignal){
|
|
|
528
549
|
let failure=null;
|
|
529
550
|
let detached=false;
|
|
530
551
|
|
|
531
|
-
function
|
|
552
|
+
function forwardBuiltInAIStreamAbort(){
|
|
532
553
|
if(!controller.signal.aborted){
|
|
533
554
|
controller.abort(sourceSignal?.reason);
|
|
534
555
|
}
|
|
535
556
|
}
|
|
536
557
|
|
|
537
|
-
function
|
|
558
|
+
function detachBuiltInAIStreamAbort(){
|
|
538
559
|
if(detached){
|
|
539
560
|
return;
|
|
540
561
|
}
|
|
541
562
|
detached=true;
|
|
542
563
|
sourceSignal?.removeEventListener?.(
|
|
543
564
|
'abort',
|
|
544
|
-
|
|
565
|
+
forwardBuiltInAIStreamAbort
|
|
545
566
|
);
|
|
546
567
|
}
|
|
547
568
|
|
|
548
569
|
if(sourceSignal?.aborted){
|
|
549
|
-
|
|
570
|
+
forwardBuiltInAIStreamAbort();
|
|
550
571
|
}else{
|
|
551
572
|
sourceSignal?.addEventListener?.(
|
|
552
573
|
'abort',
|
|
553
|
-
|
|
574
|
+
forwardBuiltInAIStreamAbort,
|
|
554
575
|
{once:true}
|
|
555
576
|
);
|
|
556
577
|
}
|
|
557
578
|
|
|
558
|
-
function
|
|
579
|
+
function emitBuiltInAIStreamChunk(chunk){
|
|
559
580
|
if(complete){
|
|
560
581
|
return false;
|
|
561
582
|
}
|
|
@@ -568,13 +589,13 @@ function createLegacyAIStreamBridge(execute,sourceSignal){
|
|
|
568
589
|
return true;
|
|
569
590
|
}
|
|
570
591
|
|
|
571
|
-
function
|
|
592
|
+
function finishBuiltInAIStream(error){
|
|
572
593
|
if(complete){
|
|
573
594
|
return;
|
|
574
595
|
}
|
|
575
596
|
complete=true;
|
|
576
597
|
failure=error||null;
|
|
577
|
-
|
|
598
|
+
detachBuiltInAIStreamAbort();
|
|
578
599
|
while(waiters.length){
|
|
579
600
|
const waiter=waiters.shift();
|
|
580
601
|
if(failure){
|
|
@@ -586,42 +607,42 @@ function createLegacyAIStreamBridge(execute,sourceSignal){
|
|
|
586
607
|
}
|
|
587
608
|
|
|
588
609
|
const result=Promise.resolve().then(
|
|
589
|
-
function
|
|
610
|
+
function executeBuiltInAIStream(){
|
|
590
611
|
if(controller.signal.aborted){
|
|
591
612
|
throw normalizeAIRequestAbort(controller.signal.reason);
|
|
592
613
|
}
|
|
593
614
|
return execute({
|
|
594
|
-
emit:
|
|
615
|
+
emit:emitBuiltInAIStreamChunk,
|
|
595
616
|
signal:controller.signal
|
|
596
617
|
});
|
|
597
618
|
}
|
|
598
619
|
).then(
|
|
599
|
-
function
|
|
600
|
-
|
|
620
|
+
function acceptBuiltInAIStreamResult(value){
|
|
621
|
+
finishBuiltInAIStream(null);
|
|
601
622
|
return value;
|
|
602
623
|
},
|
|
603
|
-
function
|
|
624
|
+
function rejectBuiltInAIStreamResult(error){
|
|
604
625
|
const normalized=isAIRequestAbort(error,controller.signal)
|
|
605
626
|
?normalizeAIRequestAbort(error)
|
|
606
627
|
:error;
|
|
607
|
-
|
|
628
|
+
finishBuiltInAIStream(normalized);
|
|
608
629
|
throw normalized;
|
|
609
630
|
}
|
|
610
631
|
);
|
|
611
|
-
result.catch(function
|
|
632
|
+
result.catch(function retainBuiltInAIStreamFailure() {});
|
|
612
633
|
|
|
613
|
-
async function
|
|
634
|
+
async function cancelBuiltInAIStream(reason){
|
|
614
635
|
if(!controller.signal.aborted){
|
|
615
636
|
controller.abort(reason);
|
|
616
637
|
}
|
|
617
|
-
await result.catch(function
|
|
638
|
+
await result.catch(function retainCancelledBuiltInAIStream() {});
|
|
618
639
|
return true;
|
|
619
640
|
}
|
|
620
641
|
|
|
621
642
|
const handle={
|
|
622
643
|
result,
|
|
623
|
-
cancel:
|
|
624
|
-
next:function
|
|
644
|
+
cancel:cancelBuiltInAIStream,
|
|
645
|
+
next:function readBuiltInAIStreamChunk(){
|
|
625
646
|
if(queue.length){
|
|
626
647
|
return Promise.resolve({value:queue.shift(),done:false});
|
|
627
648
|
}
|
|
@@ -630,24 +651,24 @@ function createLegacyAIStreamBridge(execute,sourceSignal){
|
|
|
630
651
|
?Promise.reject(failure)
|
|
631
652
|
:Promise.resolve({value:undefined,done:true});
|
|
632
653
|
}
|
|
633
|
-
return new Promise(function
|
|
654
|
+
return new Promise(function waitForBuiltInAIStreamChunk(resolve,reject){
|
|
634
655
|
waiters.push({resolve,reject});
|
|
635
656
|
});
|
|
636
657
|
},
|
|
637
|
-
return:async function
|
|
638
|
-
await
|
|
639
|
-
|
|
640
|
-
'The
|
|
658
|
+
return:async function returnBuiltInAIStream(value){
|
|
659
|
+
await cancelBuiltInAIStream(
|
|
660
|
+
aiProviderError(
|
|
661
|
+
'The built-in AI stream consumer stopped before completion.',
|
|
641
662
|
'ARCANE_AI_REQUEST_ABORTED'
|
|
642
663
|
)
|
|
643
664
|
);
|
|
644
665
|
return {value,done:true};
|
|
645
666
|
},
|
|
646
|
-
throw:async function
|
|
647
|
-
await
|
|
667
|
+
throw:async function throwBuiltInAIStream(error){
|
|
668
|
+
await cancelBuiltInAIStream(error);
|
|
648
669
|
throw error;
|
|
649
670
|
},
|
|
650
|
-
[Symbol.asyncIterator]:function
|
|
671
|
+
[Symbol.asyncIterator]:function iterateBuiltInAIStream(){
|
|
651
672
|
return this;
|
|
652
673
|
}
|
|
653
674
|
};
|
|
@@ -788,11 +809,46 @@ function browserSpeechIdentifier(value,label){
|
|
|
788
809
|
return value;
|
|
789
810
|
}
|
|
790
811
|
|
|
812
|
+
function normalizeBrowserTTSExecution(value){
|
|
813
|
+
if(value===undefined){
|
|
814
|
+
return completeValue({...DEFAULT_BROWSER_TTS_EXECUTION});
|
|
815
|
+
}
|
|
816
|
+
const descriptors=closedRecord(
|
|
817
|
+
value,
|
|
818
|
+
['device','maxConcurrentRequests'],
|
|
819
|
+
[],
|
|
820
|
+
'AI browser speech tts.execution'
|
|
821
|
+
);
|
|
822
|
+
const device=descriptors.device
|
|
823
|
+
?descriptors.device.value
|
|
824
|
+
:DEFAULT_BROWSER_TTS_EXECUTION.device;
|
|
825
|
+
const maxConcurrentRequests=descriptors.maxConcurrentRequests
|
|
826
|
+
?descriptors.maxConcurrentRequests.value
|
|
827
|
+
:DEFAULT_BROWSER_TTS_EXECUTION.maxConcurrentRequests;
|
|
828
|
+
if(typeof device!=='string'||!BROWSER_TTS_EXECUTION_DEVICES.has(device)){
|
|
829
|
+
throw aiBrowserSpeechError(
|
|
830
|
+
AI_BROWSER_SPEECH_ERROR_CODES.configurationContractMismatch,
|
|
831
|
+
AI_BROWSER_SPEECH_REASONS.configurationContractMismatch,
|
|
832
|
+
'AI browser speech tts.execution.device must be auto, webgpu, or wasm.'
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
if(!Number.isSafeInteger(maxConcurrentRequests)
|
|
836
|
+
||maxConcurrentRequests<1
|
|
837
|
+
||maxConcurrentRequests>MAX_BROWSER_TTS_CONCURRENT_REQUESTS){
|
|
838
|
+
throw aiBrowserSpeechError(
|
|
839
|
+
AI_BROWSER_SPEECH_ERROR_CODES.configurationContractMismatch,
|
|
840
|
+
AI_BROWSER_SPEECH_REASONS.configurationContractMismatch,
|
|
841
|
+
`AI browser speech tts.execution.maxConcurrentRequests must be an integer from 1 through ${MAX_BROWSER_TTS_CONCURRENT_REQUESTS}.`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
return completeValue({device,maxConcurrentRequests});
|
|
845
|
+
}
|
|
846
|
+
|
|
791
847
|
function normalizeBrowserSpeechRole(value,role){
|
|
792
848
|
const label=`AI browser speech ${role}`;
|
|
793
849
|
const descriptors=closedRecord(
|
|
794
850
|
value,
|
|
795
|
-
['providerId','graph','model','runtime','security','offline'],
|
|
851
|
+
['providerId','graph','model','runtime','security','offline','execution'],
|
|
796
852
|
['providerId','offline'],
|
|
797
853
|
label
|
|
798
854
|
);
|
|
@@ -853,6 +909,13 @@ function normalizeBrowserSpeechRole(value,role){
|
|
|
853
909
|
`${label}.offline must be a boolean.`
|
|
854
910
|
);
|
|
855
911
|
}
|
|
912
|
+
if(role==='stt'&&descriptors.execution){
|
|
913
|
+
throw aiBrowserSpeechError(
|
|
914
|
+
AI_BROWSER_SPEECH_ERROR_CODES.configurationContractMismatch,
|
|
915
|
+
AI_BROWSER_SPEECH_REASONS.configurationContractMismatch,
|
|
916
|
+
'AI browser speech execution policy is available only for TTS.'
|
|
917
|
+
);
|
|
918
|
+
}
|
|
856
919
|
return completeValue({
|
|
857
920
|
providerId,
|
|
858
921
|
...(hasGraph
|
|
@@ -865,7 +928,12 @@ function normalizeBrowserSpeechRole(value,role){
|
|
|
865
928
|
runtime:descriptors.runtime.value,
|
|
866
929
|
...(secure?{security:{secure:true}}:{})
|
|
867
930
|
}),
|
|
868
|
-
offline:descriptors.offline.value
|
|
931
|
+
offline:descriptors.offline.value,
|
|
932
|
+
...(role==='tts'
|
|
933
|
+
?{execution:normalizeBrowserTTSExecution(
|
|
934
|
+
descriptors.execution?.value
|
|
935
|
+
)}
|
|
936
|
+
:{})
|
|
869
937
|
});
|
|
870
938
|
}
|
|
871
939
|
|
|
@@ -959,9 +1027,8 @@ class AI {
|
|
|
959
1027
|
// This is the enum section for inference configuration
|
|
960
1028
|
#service = {
|
|
961
1029
|
baseURL: {
|
|
962
|
-
//
|
|
963
|
-
|
|
964
|
-
OPENAI: 'https://inference.do-ai.run/v1'
|
|
1030
|
+
// TWiN Cloud owns this remote LLM route.
|
|
1031
|
+
TWIN: 'https://inference.do-ai.run/v1'
|
|
965
1032
|
},
|
|
966
1033
|
sttURL: {
|
|
967
1034
|
LOCAL_SPEACH: 'http://127.0.0.1:8011/v1'
|
|
@@ -973,7 +1040,7 @@ class AI {
|
|
|
973
1040
|
|
|
974
1041
|
#paths = {
|
|
975
1042
|
chat: {
|
|
976
|
-
|
|
1043
|
+
TWIN: '/chat/completions'
|
|
977
1044
|
},
|
|
978
1045
|
stt: {
|
|
979
1046
|
LOCAL_SPEACH: '/audio/transcriptions'
|
|
@@ -984,7 +1051,7 @@ class AI {
|
|
|
984
1051
|
}
|
|
985
1052
|
|
|
986
1053
|
#models = {
|
|
987
|
-
|
|
1054
|
+
TWIN:'openai-gpt-oss-120b'
|
|
988
1055
|
}
|
|
989
1056
|
|
|
990
1057
|
#sttModels = {
|
|
@@ -1003,7 +1070,7 @@ class AI {
|
|
|
1003
1070
|
// Note: if we expand cloud providers, simply add their expected JSON metadata here
|
|
1004
1071
|
get #serviceHeaders(){
|
|
1005
1072
|
return {
|
|
1006
|
-
|
|
1073
|
+
TWIN: {
|
|
1007
1074
|
'Content-Type': 'application/json',
|
|
1008
1075
|
'Authorization': `Bearer ${this.twinKey}`
|
|
1009
1076
|
}
|
|
@@ -1071,10 +1138,10 @@ class AI {
|
|
|
1071
1138
|
);
|
|
1072
1139
|
|
|
1073
1140
|
const preferences=[
|
|
1074
|
-
llmService||'
|
|
1141
|
+
llmService||'TWIN',
|
|
1075
1142
|
sttService||'LOCAL_SPEACH',
|
|
1076
1143
|
ttsService||'LOCAL_SPEACH',
|
|
1077
|
-
model||'
|
|
1144
|
+
model||'TWIN',
|
|
1078
1145
|
modelTTS||'LOCAL_SPEACH',
|
|
1079
1146
|
modelSTT||'LOCAL_SPEACH'
|
|
1080
1147
|
];
|
|
@@ -1085,9 +1152,9 @@ class AI {
|
|
|
1085
1152
|
const runtime=this;
|
|
1086
1153
|
this.#stopOllamaReady=arcaneEvents.subscribe(
|
|
1087
1154
|
'arcane-ollama-ready',
|
|
1088
|
-
function
|
|
1089
|
-
runtime.#
|
|
1090
|
-
runtime.#
|
|
1155
|
+
function reconcileBuiltInOllamaReadiness(){
|
|
1156
|
+
runtime.#retainBuiltInLLMReadiness(
|
|
1157
|
+
runtime.#reconcileBuiltInLLMReadiness()
|
|
1091
1158
|
);
|
|
1092
1159
|
}
|
|
1093
1160
|
);
|
|
@@ -1102,21 +1169,21 @@ class AI {
|
|
|
1102
1169
|
#browserSpeechModulePromise=null;
|
|
1103
1170
|
#browserSpeechOperationSequence=0;
|
|
1104
1171
|
#browserSpeechRetiredRecords=new Set();
|
|
1105
|
-
#
|
|
1172
|
+
#browserSpeechRetiredBuiltInRecords=new Set();
|
|
1106
1173
|
#browserSpeechTransition=Promise.resolve();
|
|
1107
|
-
#
|
|
1108
|
-
#
|
|
1109
|
-
#
|
|
1110
|
-
#
|
|
1174
|
+
#builtInLLMProviders=new Map();
|
|
1175
|
+
#builtInLLMReadiness=Promise.resolve(null);
|
|
1176
|
+
#builtInSpeechProviders=new Map();
|
|
1177
|
+
#builtInSpeechReadiness=Promise.resolve(null);
|
|
1111
1178
|
#speechControlGeneration=0;
|
|
1112
1179
|
#speechFailureSequence=0;
|
|
1113
1180
|
#stopOllamaReady=null;
|
|
1114
1181
|
#ttsSegmentation={...DEFAULT_TTS_SEGMENTATION};
|
|
1115
1182
|
#preferenceTuple=completeValue([
|
|
1116
|
-
'
|
|
1183
|
+
'TWIN',
|
|
1117
1184
|
'LOCAL_SPEACH',
|
|
1118
1185
|
'LOCAL_SPEACH',
|
|
1119
|
-
'
|
|
1186
|
+
'TWIN',
|
|
1120
1187
|
'LOCAL_SPEACH',
|
|
1121
1188
|
'LOCAL_SPEACH'
|
|
1122
1189
|
]);
|
|
@@ -1163,26 +1230,14 @@ class AI {
|
|
|
1163
1230
|
return `${this.#service.baseURL[this.llmService]}${this.#paths.chat[this.llmService]}`
|
|
1164
1231
|
}
|
|
1165
1232
|
|
|
1166
|
-
set url(value) {
|
|
1167
|
-
return false;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
1233
|
get urlTTS() {
|
|
1171
1234
|
return `${this.#service.ttsURL[this.ttsService]}${this.#paths.tts[this.ttsService]}`
|
|
1172
1235
|
}
|
|
1173
1236
|
|
|
1174
|
-
set urlTTS(value) {
|
|
1175
|
-
return false;
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
1237
|
get urlSTT() {
|
|
1179
1238
|
return `${this.#service.sttURL[this.sttService]}${this.#paths.stt[this.sttService]}`
|
|
1180
1239
|
}
|
|
1181
1240
|
|
|
1182
|
-
set urlSTT(value) {
|
|
1183
|
-
return false;
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
1241
|
#license='';
|
|
1187
1242
|
|
|
1188
1243
|
// Browser-delivered framework code must not contain provider credentials.
|
|
@@ -1195,8 +1250,8 @@ class AI {
|
|
|
1195
1250
|
|
|
1196
1251
|
set twinKey(value){
|
|
1197
1252
|
this.#license=typeof value==='string' ? value.trim():'';
|
|
1198
|
-
this.#
|
|
1199
|
-
this.#
|
|
1253
|
+
this.#retainBuiltInLLMReadiness(
|
|
1254
|
+
this.#reconcileBuiltInLLMReadiness()
|
|
1200
1255
|
);
|
|
1201
1256
|
return this.#license;
|
|
1202
1257
|
}
|
|
@@ -1212,9 +1267,9 @@ class AI {
|
|
|
1212
1267
|
return this.#license;
|
|
1213
1268
|
}
|
|
1214
1269
|
|
|
1215
|
-
#
|
|
1216
|
-
if(providerId==='
|
|
1217
|
-
return this.llmService==='
|
|
1270
|
+
#builtInLLMCapability(providerId){
|
|
1271
|
+
if(providerId==='TWIN'){
|
|
1272
|
+
return this.llmService==='TWIN'
|
|
1218
1273
|
&&Boolean(this.model)
|
|
1219
1274
|
&&Boolean(this.license)
|
|
1220
1275
|
&&typeof globalThis.fetch==='function';
|
|
@@ -1227,7 +1282,7 @@ class AI {
|
|
|
1227
1282
|
return false;
|
|
1228
1283
|
}
|
|
1229
1284
|
|
|
1230
|
-
#
|
|
1285
|
+
#builtInLLMInspection(providerId,selection){
|
|
1231
1286
|
const localOnly=providerId==='OLLAMA';
|
|
1232
1287
|
if(!selection
|
|
1233
1288
|
||selection.providerId!==providerId
|
|
@@ -1237,10 +1292,10 @@ class AI {
|
|
|
1237
1292
|
return completeValue({
|
|
1238
1293
|
available:false,
|
|
1239
1294
|
code:'ARCANE_AI_MODEL_AUTHORITY_REQUIRED',
|
|
1240
|
-
message:'The selected
|
|
1295
|
+
message:'The selected built-in LLM route does not match the active AI configuration.'
|
|
1241
1296
|
});
|
|
1242
1297
|
}
|
|
1243
|
-
if(!this.#
|
|
1298
|
+
if(!this.#builtInLLMCapability(providerId)){
|
|
1244
1299
|
return completeValue({
|
|
1245
1300
|
available:false,
|
|
1246
1301
|
code:providerId==='OLLAMA'
|
|
@@ -1261,16 +1316,16 @@ class AI {
|
|
|
1261
1316
|
});
|
|
1262
1317
|
}
|
|
1263
1318
|
|
|
1264
|
-
#
|
|
1319
|
+
#createBuiltInLLMProvider(providerId){
|
|
1265
1320
|
const runtime=this;
|
|
1266
1321
|
const localOnly=providerId==='OLLAMA';
|
|
1267
1322
|
let state='unloaded';
|
|
1268
1323
|
let busy=false;
|
|
1269
1324
|
|
|
1270
|
-
function
|
|
1325
|
+
function statusBuiltInLLMProvider(){
|
|
1271
1326
|
if(state==='ready'
|
|
1272
1327
|
&&!busy
|
|
1273
|
-
&&!runtime.#
|
|
1328
|
+
&&!runtime.#builtInLLMCapability(providerId)){
|
|
1274
1329
|
state='unloaded';
|
|
1275
1330
|
}
|
|
1276
1331
|
return completeValue({
|
|
@@ -1280,13 +1335,13 @@ class AI {
|
|
|
1280
1335
|
});
|
|
1281
1336
|
}
|
|
1282
1337
|
|
|
1283
|
-
function
|
|
1284
|
-
const inspection=runtime.#
|
|
1338
|
+
function assertBuiltInLLMSelection(selection){
|
|
1339
|
+
const inspection=runtime.#builtInLLMInspection(
|
|
1285
1340
|
providerId,
|
|
1286
1341
|
selection
|
|
1287
1342
|
);
|
|
1288
1343
|
if(!inspection.available){
|
|
1289
|
-
throw
|
|
1344
|
+
throw aiProviderError(
|
|
1290
1345
|
inspection.message,
|
|
1291
1346
|
inspection.code
|
|
1292
1347
|
);
|
|
@@ -1294,7 +1349,7 @@ class AI {
|
|
|
1294
1349
|
return inspection;
|
|
1295
1350
|
}
|
|
1296
1351
|
|
|
1297
|
-
function
|
|
1352
|
+
function releaseBuiltInLLMRequest(){
|
|
1298
1353
|
busy=false;
|
|
1299
1354
|
}
|
|
1300
1355
|
|
|
@@ -1303,7 +1358,7 @@ class AI {
|
|
|
1303
1358
|
role:'llm',
|
|
1304
1359
|
id:providerId,
|
|
1305
1360
|
localOnly,
|
|
1306
|
-
catalog:function
|
|
1361
|
+
catalog:function catalogBuiltInLLMProvider(){
|
|
1307
1362
|
if(runtime.llmService!==providerId||!runtime.model){
|
|
1308
1363
|
return completeValue([]);
|
|
1309
1364
|
}
|
|
@@ -1311,35 +1366,35 @@ class AI {
|
|
|
1311
1366
|
completeValue({id:runtime.model})
|
|
1312
1367
|
]);
|
|
1313
1368
|
},
|
|
1314
|
-
inspect:function
|
|
1369
|
+
inspect:function inspectBuiltInLLMProvider(selection,{signal}={}){
|
|
1315
1370
|
if(signal?.aborted){
|
|
1316
1371
|
throw normalizeAIRequestAbort(signal.reason);
|
|
1317
1372
|
}
|
|
1318
|
-
return runtime.#
|
|
1373
|
+
return runtime.#builtInLLMInspection(providerId,selection);
|
|
1319
1374
|
},
|
|
1320
|
-
status:
|
|
1321
|
-
load:function
|
|
1375
|
+
status:statusBuiltInLLMProvider,
|
|
1376
|
+
load:function loadBuiltInLLMProvider(context={}){
|
|
1322
1377
|
if(context.signal?.aborted){
|
|
1323
1378
|
throw normalizeAIRequestAbort(context.signal.reason);
|
|
1324
1379
|
}
|
|
1325
1380
|
if(state==='disposed'){
|
|
1326
|
-
throw
|
|
1327
|
-
'The
|
|
1381
|
+
throw aiProviderError(
|
|
1382
|
+
'The built-in LLM provider is disposed.',
|
|
1328
1383
|
'ARCANE_AI_PROVIDER_DISPOSED'
|
|
1329
1384
|
);
|
|
1330
1385
|
}
|
|
1331
1386
|
if(busy){
|
|
1332
|
-
throw
|
|
1333
|
-
'The
|
|
1387
|
+
throw aiProviderError(
|
|
1388
|
+
'The built-in LLM provider owns an active request.',
|
|
1334
1389
|
'ARCANE_AI_ROLE_BUSY'
|
|
1335
1390
|
);
|
|
1336
1391
|
}
|
|
1337
1392
|
if(typeof context.progress!=='function'){
|
|
1338
1393
|
throw new TypeError(
|
|
1339
|
-
'
|
|
1394
|
+
'Built-in LLM provider load progress must be a function.'
|
|
1340
1395
|
);
|
|
1341
1396
|
}
|
|
1342
|
-
const inspection=
|
|
1397
|
+
const inspection=assertBuiltInLLMSelection(context.selection);
|
|
1343
1398
|
state='loading';
|
|
1344
1399
|
context.progress({
|
|
1345
1400
|
phase:'capability',
|
|
@@ -1362,40 +1417,40 @@ class AI {
|
|
|
1362
1417
|
});
|
|
1363
1418
|
return completeValue({
|
|
1364
1419
|
authority:inspection.authority,
|
|
1365
|
-
status:
|
|
1420
|
+
status:statusBuiltInLLMProvider()
|
|
1366
1421
|
});
|
|
1367
1422
|
},
|
|
1368
|
-
request:function
|
|
1423
|
+
request:function requestBuiltInLLMProvider(context={}){
|
|
1369
1424
|
if(context.signal?.aborted){
|
|
1370
1425
|
throw normalizeAIRequestAbort(context.signal.reason);
|
|
1371
1426
|
}
|
|
1372
|
-
|
|
1373
|
-
const current=
|
|
1427
|
+
assertBuiltInLLMSelection(context.selection);
|
|
1428
|
+
const current=statusBuiltInLLMProvider();
|
|
1374
1429
|
if(current.state!=='ready'||!current.loaded){
|
|
1375
|
-
throw
|
|
1376
|
-
'The
|
|
1430
|
+
throw aiProviderError(
|
|
1431
|
+
'The built-in LLM provider is not ready.',
|
|
1377
1432
|
'ARCANE_AI_ROLE_NOT_READY'
|
|
1378
1433
|
);
|
|
1379
1434
|
}
|
|
1380
1435
|
if(busy){
|
|
1381
|
-
throw
|
|
1382
|
-
'The
|
|
1436
|
+
throw aiProviderError(
|
|
1437
|
+
'The built-in LLM provider owns an active request.',
|
|
1383
1438
|
'ARCANE_AI_ROLE_BUSY'
|
|
1384
1439
|
);
|
|
1385
1440
|
}
|
|
1386
1441
|
busy=true;
|
|
1387
1442
|
if(context.operation==='chat'){
|
|
1388
1443
|
return Promise.resolve(
|
|
1389
|
-
runtime.#
|
|
1444
|
+
runtime.#requestBuiltInLLMChat(
|
|
1390
1445
|
context.payload,
|
|
1391
1446
|
context.signal
|
|
1392
1447
|
)
|
|
1393
|
-
).finally(
|
|
1448
|
+
).finally(releaseBuiltInLLMRequest);
|
|
1394
1449
|
}
|
|
1395
1450
|
if(context.operation==='stream'){
|
|
1396
|
-
const handle=
|
|
1397
|
-
function
|
|
1398
|
-
return runtime.#
|
|
1451
|
+
const handle=createBuiltInAIStreamBridge(
|
|
1452
|
+
function executeBuiltInLLMProviderStream(bridge){
|
|
1453
|
+
return runtime.#requestBuiltInLLMStream(
|
|
1399
1454
|
context.payload,
|
|
1400
1455
|
bridge
|
|
1401
1456
|
);
|
|
@@ -1403,49 +1458,49 @@ class AI {
|
|
|
1403
1458
|
context.signal
|
|
1404
1459
|
);
|
|
1405
1460
|
handle.result.then(
|
|
1406
|
-
|
|
1407
|
-
|
|
1461
|
+
releaseBuiltInLLMRequest,
|
|
1462
|
+
releaseBuiltInLLMRequest
|
|
1408
1463
|
);
|
|
1409
1464
|
return handle;
|
|
1410
1465
|
}
|
|
1411
1466
|
busy=false;
|
|
1412
|
-
throw
|
|
1413
|
-
'The
|
|
1467
|
+
throw aiProviderError(
|
|
1468
|
+
'The built-in LLM provider operation is unsupported.',
|
|
1414
1469
|
'ARCANE_AI_PROVIDER_RUNTIME_INVALID'
|
|
1415
1470
|
);
|
|
1416
1471
|
},
|
|
1417
|
-
unload:function
|
|
1472
|
+
unload:function unloadBuiltInLLMProvider(context={}){
|
|
1418
1473
|
if(context.signal?.aborted){
|
|
1419
1474
|
throw normalizeAIRequestAbort(context.signal.reason);
|
|
1420
1475
|
}
|
|
1421
1476
|
state='unloaded';
|
|
1422
1477
|
busy=false;
|
|
1423
|
-
return
|
|
1478
|
+
return statusBuiltInLLMProvider();
|
|
1424
1479
|
},
|
|
1425
|
-
dispose:function
|
|
1480
|
+
dispose:function disposeBuiltInLLMProvider(context={}){
|
|
1426
1481
|
if(context.signal?.aborted){
|
|
1427
1482
|
throw normalizeAIRequestAbort(context.signal.reason);
|
|
1428
1483
|
}
|
|
1429
1484
|
state='disposed';
|
|
1430
1485
|
busy=false;
|
|
1431
|
-
return
|
|
1486
|
+
return statusBuiltInLLMProvider();
|
|
1432
1487
|
}
|
|
1433
1488
|
});
|
|
1434
1489
|
}
|
|
1435
1490
|
|
|
1436
|
-
#
|
|
1491
|
+
#builtInSpeechService(role){
|
|
1437
1492
|
return role==='stt'?this.sttService:this.ttsService;
|
|
1438
1493
|
}
|
|
1439
1494
|
|
|
1440
|
-
#
|
|
1495
|
+
#builtInSpeechModel(role){
|
|
1441
1496
|
return role==='stt'?this.modelSTT:this.modelTTS;
|
|
1442
1497
|
}
|
|
1443
1498
|
|
|
1444
|
-
#
|
|
1499
|
+
#builtInSpeechProviderKey(role,providerId){
|
|
1445
1500
|
return `${role}:${providerId}`;
|
|
1446
1501
|
}
|
|
1447
1502
|
|
|
1448
|
-
#
|
|
1503
|
+
#builtInSpeechDefaultVoice(role,providerId){
|
|
1449
1504
|
if(role!=='tts'){
|
|
1450
1505
|
return null;
|
|
1451
1506
|
}
|
|
@@ -1455,9 +1510,9 @@ class AI {
|
|
|
1455
1510
|
return null;
|
|
1456
1511
|
}
|
|
1457
1512
|
|
|
1458
|
-
#
|
|
1459
|
-
const service=this.#
|
|
1460
|
-
const model=this.#
|
|
1513
|
+
#builtInSpeechCapability(role,providerId){
|
|
1514
|
+
const service=this.#builtInSpeechService(role);
|
|
1515
|
+
const model=this.#builtInSpeechModel(role);
|
|
1461
1516
|
if(service!==providerId||!model){
|
|
1462
1517
|
return false;
|
|
1463
1518
|
}
|
|
@@ -1467,20 +1522,20 @@ class AI {
|
|
|
1467
1522
|
return false;
|
|
1468
1523
|
}
|
|
1469
1524
|
|
|
1470
|
-
#
|
|
1525
|
+
#builtInSpeechInspection(role,providerId,selection){
|
|
1471
1526
|
const localOnly=providerId==='LOCAL_SPEACH';
|
|
1472
1527
|
if(!selection
|
|
1473
1528
|
||selection.providerId!==providerId
|
|
1474
|
-
||selection.modelId!==this.#
|
|
1529
|
+
||selection.modelId!==this.#builtInSpeechModel(role)
|
|
1475
1530
|
||selection.localOnly!==localOnly
|
|
1476
|
-
||this.#
|
|
1531
|
+
||this.#builtInSpeechService(role)!==providerId){
|
|
1477
1532
|
return completeValue({
|
|
1478
1533
|
available:false,
|
|
1479
1534
|
code:'ARCANE_AI_MODEL_AUTHORITY_REQUIRED',
|
|
1480
|
-
message:`The selected
|
|
1535
|
+
message:`The selected built-in ${role.toUpperCase()} route does not match the active AI configuration.`
|
|
1481
1536
|
});
|
|
1482
1537
|
}
|
|
1483
|
-
if(!this.#
|
|
1538
|
+
if(!this.#builtInSpeechCapability(role,providerId)){
|
|
1484
1539
|
return completeValue({
|
|
1485
1540
|
available:false,
|
|
1486
1541
|
code:providerId==='LOCAL_SPEACH'
|
|
@@ -1501,17 +1556,17 @@ class AI {
|
|
|
1501
1556
|
});
|
|
1502
1557
|
}
|
|
1503
1558
|
|
|
1504
|
-
#
|
|
1559
|
+
#createBuiltInSpeechProvider(role,providerId){
|
|
1505
1560
|
const runtime=this;
|
|
1506
1561
|
const localOnly=providerId==='LOCAL_SPEACH';
|
|
1507
1562
|
const expectedOperation=role==='stt'?'transcribe':'synthesize';
|
|
1508
1563
|
let state='unloaded';
|
|
1509
1564
|
let busy=false;
|
|
1510
1565
|
|
|
1511
|
-
function
|
|
1566
|
+
function statusBuiltInSpeechProvider(){
|
|
1512
1567
|
if(state==='ready'
|
|
1513
1568
|
&&!busy
|
|
1514
|
-
&&!runtime.#
|
|
1569
|
+
&&!runtime.#builtInSpeechCapability(role,providerId)){
|
|
1515
1570
|
state='unloaded';
|
|
1516
1571
|
}
|
|
1517
1572
|
return completeValue({
|
|
@@ -1521,14 +1576,14 @@ class AI {
|
|
|
1521
1576
|
});
|
|
1522
1577
|
}
|
|
1523
1578
|
|
|
1524
|
-
function
|
|
1525
|
-
const inspection=runtime.#
|
|
1579
|
+
function assertBuiltInSpeechSelection(selection){
|
|
1580
|
+
const inspection=runtime.#builtInSpeechInspection(
|
|
1526
1581
|
role,
|
|
1527
1582
|
providerId,
|
|
1528
1583
|
selection
|
|
1529
1584
|
);
|
|
1530
1585
|
if(!inspection.available){
|
|
1531
|
-
throw
|
|
1586
|
+
throw aiProviderError(
|
|
1532
1587
|
inspection.message,
|
|
1533
1588
|
inspection.code
|
|
1534
1589
|
);
|
|
@@ -1536,7 +1591,7 @@ class AI {
|
|
|
1536
1591
|
return inspection;
|
|
1537
1592
|
}
|
|
1538
1593
|
|
|
1539
|
-
function
|
|
1594
|
+
function releaseBuiltInSpeechRequest(){
|
|
1540
1595
|
busy=false;
|
|
1541
1596
|
}
|
|
1542
1597
|
|
|
@@ -1545,12 +1600,12 @@ class AI {
|
|
|
1545
1600
|
role,
|
|
1546
1601
|
id:providerId,
|
|
1547
1602
|
localOnly,
|
|
1548
|
-
catalog:function
|
|
1549
|
-
const model=runtime.#
|
|
1550
|
-
if(runtime.#
|
|
1603
|
+
catalog:function catalogBuiltInSpeechProvider(){
|
|
1604
|
+
const model=runtime.#builtInSpeechModel(role);
|
|
1605
|
+
if(runtime.#builtInSpeechService(role)!==providerId||!model){
|
|
1551
1606
|
return completeValue([]);
|
|
1552
1607
|
}
|
|
1553
|
-
const defaultVoice=runtime.#
|
|
1608
|
+
const defaultVoice=runtime.#builtInSpeechDefaultVoice(
|
|
1554
1609
|
role,
|
|
1555
1610
|
providerId
|
|
1556
1611
|
);
|
|
@@ -1561,35 +1616,35 @@ class AI {
|
|
|
1561
1616
|
})
|
|
1562
1617
|
]);
|
|
1563
1618
|
},
|
|
1564
|
-
inspect:function
|
|
1619
|
+
inspect:function inspectBuiltInSpeechProvider(selection,{signal}={}){
|
|
1565
1620
|
if(signal?.aborted){
|
|
1566
1621
|
throw normalizeAIRequestAbort(signal.reason);
|
|
1567
1622
|
}
|
|
1568
|
-
return runtime.#
|
|
1623
|
+
return runtime.#builtInSpeechInspection(role,providerId,selection);
|
|
1569
1624
|
},
|
|
1570
|
-
status:
|
|
1571
|
-
load:function
|
|
1625
|
+
status:statusBuiltInSpeechProvider,
|
|
1626
|
+
load:function loadBuiltInSpeechProvider(context={}){
|
|
1572
1627
|
if(context.signal?.aborted){
|
|
1573
1628
|
throw normalizeAIRequestAbort(context.signal.reason);
|
|
1574
1629
|
}
|
|
1575
1630
|
if(state==='disposed'){
|
|
1576
|
-
throw
|
|
1577
|
-
`The
|
|
1631
|
+
throw aiProviderError(
|
|
1632
|
+
`The built-in ${role.toUpperCase()} provider is disposed.`,
|
|
1578
1633
|
'ARCANE_AI_PROVIDER_DISPOSED'
|
|
1579
1634
|
);
|
|
1580
1635
|
}
|
|
1581
1636
|
if(busy){
|
|
1582
|
-
throw
|
|
1583
|
-
`The
|
|
1637
|
+
throw aiProviderError(
|
|
1638
|
+
`The built-in ${role.toUpperCase()} provider owns an active request.`,
|
|
1584
1639
|
'ARCANE_AI_ROLE_BUSY'
|
|
1585
1640
|
);
|
|
1586
1641
|
}
|
|
1587
1642
|
if(typeof context.progress!=='function'){
|
|
1588
1643
|
throw new TypeError(
|
|
1589
|
-
`
|
|
1644
|
+
`Built-in ${role.toUpperCase()} provider load progress must be a function.`
|
|
1590
1645
|
);
|
|
1591
1646
|
}
|
|
1592
|
-
const inspection=
|
|
1647
|
+
const inspection=assertBuiltInSpeechSelection(context.selection);
|
|
1593
1648
|
state='loading';
|
|
1594
1649
|
context.progress({
|
|
1595
1650
|
phase:'capability',
|
|
@@ -1612,135 +1667,135 @@ class AI {
|
|
|
1612
1667
|
});
|
|
1613
1668
|
return completeValue({
|
|
1614
1669
|
authority:inspection.authority,
|
|
1615
|
-
status:
|
|
1670
|
+
status:statusBuiltInSpeechProvider()
|
|
1616
1671
|
});
|
|
1617
1672
|
},
|
|
1618
|
-
request:function
|
|
1673
|
+
request:function requestBuiltInSpeechProvider(context={}){
|
|
1619
1674
|
if(context.signal?.aborted){
|
|
1620
1675
|
throw normalizeAIRequestAbort(context.signal.reason);
|
|
1621
1676
|
}
|
|
1622
|
-
|
|
1623
|
-
const current=
|
|
1677
|
+
assertBuiltInSpeechSelection(context.selection);
|
|
1678
|
+
const current=statusBuiltInSpeechProvider();
|
|
1624
1679
|
if(current.state!=='ready'||!current.loaded){
|
|
1625
|
-
throw
|
|
1626
|
-
`The
|
|
1680
|
+
throw aiProviderError(
|
|
1681
|
+
`The built-in ${role.toUpperCase()} provider is not ready.`,
|
|
1627
1682
|
'ARCANE_AI_ROLE_NOT_READY'
|
|
1628
1683
|
);
|
|
1629
1684
|
}
|
|
1630
1685
|
if(busy){
|
|
1631
|
-
throw
|
|
1632
|
-
`The
|
|
1686
|
+
throw aiProviderError(
|
|
1687
|
+
`The built-in ${role.toUpperCase()} provider owns an active request.`,
|
|
1633
1688
|
'ARCANE_AI_ROLE_BUSY'
|
|
1634
1689
|
);
|
|
1635
1690
|
}
|
|
1636
1691
|
if(context.operation!==expectedOperation){
|
|
1637
|
-
throw
|
|
1638
|
-
`The
|
|
1692
|
+
throw aiProviderError(
|
|
1693
|
+
`The built-in ${role.toUpperCase()} provider operation is unsupported.`,
|
|
1639
1694
|
'ARCANE_AI_PROVIDER_RUNTIME_INVALID'
|
|
1640
1695
|
);
|
|
1641
1696
|
}
|
|
1642
1697
|
busy=true;
|
|
1643
1698
|
const request=role==='stt'
|
|
1644
|
-
?runtime.#
|
|
1699
|
+
?runtime.#requestBuiltInSpeechTranscription(
|
|
1645
1700
|
context.payload,
|
|
1646
1701
|
context.signal
|
|
1647
1702
|
)
|
|
1648
|
-
:runtime.#
|
|
1703
|
+
:runtime.#requestBuiltInSpeechSynthesis(
|
|
1649
1704
|
context.payload,
|
|
1650
1705
|
context.signal
|
|
1651
1706
|
);
|
|
1652
|
-
return Promise.resolve(request).finally(
|
|
1707
|
+
return Promise.resolve(request).finally(releaseBuiltInSpeechRequest);
|
|
1653
1708
|
},
|
|
1654
|
-
unload:function
|
|
1709
|
+
unload:function unloadBuiltInSpeechProvider(context={}){
|
|
1655
1710
|
if(context.signal?.aborted){
|
|
1656
1711
|
throw normalizeAIRequestAbort(context.signal.reason);
|
|
1657
1712
|
}
|
|
1658
1713
|
if(busy){
|
|
1659
|
-
throw
|
|
1660
|
-
`The
|
|
1714
|
+
throw aiProviderError(
|
|
1715
|
+
`The built-in ${role.toUpperCase()} provider still owns an active request.`,
|
|
1661
1716
|
'ARCANE_AI_ROLE_BUSY'
|
|
1662
1717
|
);
|
|
1663
1718
|
}
|
|
1664
1719
|
state='unloaded';
|
|
1665
|
-
return
|
|
1720
|
+
return statusBuiltInSpeechProvider();
|
|
1666
1721
|
},
|
|
1667
|
-
dispose:function
|
|
1722
|
+
dispose:function disposeBuiltInSpeechProvider(context={}){
|
|
1668
1723
|
if(context.signal?.aborted){
|
|
1669
1724
|
throw normalizeAIRequestAbort(context.signal.reason);
|
|
1670
1725
|
}
|
|
1671
1726
|
if(busy){
|
|
1672
|
-
throw
|
|
1673
|
-
`The
|
|
1727
|
+
throw aiProviderError(
|
|
1728
|
+
`The built-in ${role.toUpperCase()} provider still owns an active request.`,
|
|
1674
1729
|
'ARCANE_AI_ROLE_BUSY'
|
|
1675
1730
|
);
|
|
1676
1731
|
}
|
|
1677
1732
|
state='disposed';
|
|
1678
|
-
return
|
|
1733
|
+
return statusBuiltInSpeechProvider();
|
|
1679
1734
|
}
|
|
1680
1735
|
});
|
|
1681
1736
|
}
|
|
1682
1737
|
|
|
1683
|
-
#
|
|
1684
|
-
if(providerId!=='
|
|
1738
|
+
#ensureBuiltInLLMProvider(providerId){
|
|
1739
|
+
if(providerId!=='TWIN'&&providerId!=='OLLAMA'){
|
|
1685
1740
|
return false;
|
|
1686
1741
|
}
|
|
1687
1742
|
if(this.#providerRuntime.hasProvider('llm',providerId)){
|
|
1688
1743
|
return false;
|
|
1689
1744
|
}
|
|
1690
|
-
const provider=this.#
|
|
1745
|
+
const provider=this.#createBuiltInLLMProvider(providerId);
|
|
1691
1746
|
const unregister=this.#providerRuntime.register(provider);
|
|
1692
|
-
this.#
|
|
1747
|
+
this.#builtInLLMProviders.set(
|
|
1693
1748
|
providerId,
|
|
1694
1749
|
completeValue({provider,unregister})
|
|
1695
1750
|
);
|
|
1696
1751
|
return true;
|
|
1697
1752
|
}
|
|
1698
1753
|
|
|
1699
|
-
#
|
|
1754
|
+
#ensureBuiltInSpeechProvider(role,providerId){
|
|
1700
1755
|
if(!['stt','tts'].includes(role)||providerId!=='LOCAL_SPEACH'){
|
|
1701
1756
|
return false;
|
|
1702
1757
|
}
|
|
1703
1758
|
if(this.#providerRuntime.hasProvider(role,providerId)){
|
|
1704
1759
|
return false;
|
|
1705
1760
|
}
|
|
1706
|
-
const provider=this.#
|
|
1761
|
+
const provider=this.#createBuiltInSpeechProvider(role,providerId);
|
|
1707
1762
|
const unregister=this.#providerRuntime.register(provider);
|
|
1708
|
-
this.#
|
|
1709
|
-
this.#
|
|
1763
|
+
this.#builtInSpeechProviders.set(
|
|
1764
|
+
this.#builtInSpeechProviderKey(role,providerId),
|
|
1710
1765
|
completeValue({role,providerId,provider,unregister})
|
|
1711
1766
|
);
|
|
1712
1767
|
return true;
|
|
1713
1768
|
}
|
|
1714
1769
|
|
|
1715
|
-
#
|
|
1716
|
-
for(const [providerId,record] of this.#
|
|
1770
|
+
#releaseInactiveBuiltInLLMProviders(activeProviderId){
|
|
1771
|
+
for(const [providerId,record] of this.#builtInLLMProviders){
|
|
1717
1772
|
if(providerId===activeProviderId){
|
|
1718
1773
|
continue;
|
|
1719
1774
|
}
|
|
1720
1775
|
if(record.unregister()){
|
|
1721
|
-
this.#
|
|
1776
|
+
this.#builtInLLMProviders.delete(providerId);
|
|
1722
1777
|
}
|
|
1723
1778
|
}
|
|
1724
1779
|
}
|
|
1725
1780
|
|
|
1726
|
-
#
|
|
1727
|
-
for(const [key,record] of this.#
|
|
1781
|
+
#releaseInactiveBuiltInSpeechProviders(activeProviders){
|
|
1782
|
+
for(const [key,record] of this.#builtInSpeechProviders){
|
|
1728
1783
|
if(activeProviders[record.role]===record.providerId){
|
|
1729
1784
|
continue;
|
|
1730
1785
|
}
|
|
1731
1786
|
if(record.unregister()){
|
|
1732
|
-
this.#
|
|
1787
|
+
this.#builtInSpeechProviders.delete(key);
|
|
1733
1788
|
}
|
|
1734
1789
|
}
|
|
1735
1790
|
}
|
|
1736
1791
|
|
|
1737
|
-
#
|
|
1792
|
+
#builtInLLMSelection(localOnly=false){
|
|
1738
1793
|
const selection=this.#providerRuntime.selection(
|
|
1739
1794
|
'llm',
|
|
1740
1795
|
{localOnly}
|
|
1741
1796
|
);
|
|
1742
1797
|
if(!selection
|
|
1743
|
-
||!this.#
|
|
1798
|
+
||!this.#builtInLLMProviders.has(selection.providerId)
|
|
1744
1799
|
||selection.providerId!==this.llmService
|
|
1745
1800
|
||selection.modelId!==this.model){
|
|
1746
1801
|
return null;
|
|
@@ -1748,44 +1803,44 @@ class AI {
|
|
|
1748
1803
|
return selection;
|
|
1749
1804
|
}
|
|
1750
1805
|
|
|
1751
|
-
#
|
|
1806
|
+
#builtInSpeechSelection(role,localOnly=false){
|
|
1752
1807
|
const selection=this.#providerRuntime.selection(role,{localOnly});
|
|
1753
1808
|
if(!selection
|
|
1754
|
-
||!this.#
|
|
1755
|
-
this.#
|
|
1809
|
+
||!this.#builtInSpeechProviders.has(
|
|
1810
|
+
this.#builtInSpeechProviderKey(role,selection.providerId)
|
|
1756
1811
|
)
|
|
1757
|
-
||selection.providerId!==this.#
|
|
1758
|
-
||selection.modelId!==this.#
|
|
1812
|
+
||selection.providerId!==this.#builtInSpeechService(role)
|
|
1813
|
+
||selection.modelId!==this.#builtInSpeechModel(role)){
|
|
1759
1814
|
return null;
|
|
1760
1815
|
}
|
|
1761
1816
|
return selection;
|
|
1762
1817
|
}
|
|
1763
1818
|
|
|
1764
|
-
#
|
|
1765
|
-
this.#
|
|
1766
|
-
function
|
|
1819
|
+
#retainBuiltInLLMReadiness(operation){
|
|
1820
|
+
this.#builtInLLMReadiness=Promise.resolve(operation).catch(
|
|
1821
|
+
function retainBuiltInLLMReadinessFailure(){
|
|
1767
1822
|
return null;
|
|
1768
1823
|
}
|
|
1769
1824
|
);
|
|
1770
|
-
return this.#
|
|
1825
|
+
return this.#builtInLLMReadiness;
|
|
1771
1826
|
}
|
|
1772
1827
|
|
|
1773
|
-
#
|
|
1774
|
-
this.#
|
|
1775
|
-
function
|
|
1828
|
+
#retainBuiltInSpeechReadiness(operation){
|
|
1829
|
+
this.#builtInSpeechReadiness=Promise.resolve(operation).catch(
|
|
1830
|
+
function retainBuiltInSpeechReadinessFailure(){
|
|
1776
1831
|
return null;
|
|
1777
1832
|
}
|
|
1778
1833
|
);
|
|
1779
|
-
return this.#
|
|
1834
|
+
return this.#builtInSpeechReadiness;
|
|
1780
1835
|
}
|
|
1781
1836
|
|
|
1782
|
-
#
|
|
1783
|
-
const selection=this.#
|
|
1837
|
+
#reconcileBuiltInLLMReadiness(){
|
|
1838
|
+
const selection=this.#builtInLLMSelection(false);
|
|
1784
1839
|
if(!selection){
|
|
1785
1840
|
return Promise.resolve(this.#providerRuntime.status('llm'));
|
|
1786
1841
|
}
|
|
1787
1842
|
const status=this.#providerRuntime.status('llm');
|
|
1788
|
-
if(this.#
|
|
1843
|
+
if(this.#builtInLLMCapability(selection.providerId)){
|
|
1789
1844
|
if(status.state==='ready'&&status.loaded===true){
|
|
1790
1845
|
return Promise.resolve(status);
|
|
1791
1846
|
}
|
|
@@ -1800,15 +1855,15 @@ class AI {
|
|
|
1800
1855
|
return Promise.resolve(status);
|
|
1801
1856
|
}
|
|
1802
1857
|
|
|
1803
|
-
#
|
|
1858
|
+
#reconcileBuiltInSpeechReadiness(){
|
|
1804
1859
|
const runtime=this;
|
|
1805
|
-
return Promise.all(['stt','tts'].map(function
|
|
1806
|
-
const selection=runtime.#
|
|
1860
|
+
return Promise.all(['stt','tts'].map(function reconcileBuiltInSpeechRole(role){
|
|
1861
|
+
const selection=runtime.#builtInSpeechSelection(role,false);
|
|
1807
1862
|
if(!selection){
|
|
1808
1863
|
return runtime.#providerRuntime.status(role);
|
|
1809
1864
|
}
|
|
1810
1865
|
const status=runtime.#providerRuntime.status(role);
|
|
1811
|
-
if(runtime.#
|
|
1866
|
+
if(runtime.#builtInSpeechCapability(role,selection.providerId)){
|
|
1812
1867
|
return status;
|
|
1813
1868
|
}
|
|
1814
1869
|
if(status.loaded===true
|
|
@@ -1823,8 +1878,8 @@ class AI {
|
|
|
1823
1878
|
|
|
1824
1879
|
get configured(){
|
|
1825
1880
|
if(this.#usesProviderRuntime('llm',this.llmService)){
|
|
1826
|
-
if(this.#
|
|
1827
|
-
&&!this.#
|
|
1881
|
+
if(this.#builtInLLMSelection(false)
|
|
1882
|
+
&&!this.#builtInLLMCapability(this.llmService)){
|
|
1828
1883
|
return false;
|
|
1829
1884
|
}
|
|
1830
1885
|
const state=this.#providerRuntime.status('llm');
|
|
@@ -1834,7 +1889,7 @@ class AI {
|
|
|
1834
1889
|
return Boolean(this.model)&&Boolean(this.#nativeOllama());
|
|
1835
1890
|
}
|
|
1836
1891
|
|
|
1837
|
-
return this.llmService==='
|
|
1892
|
+
return this.llmService==='TWIN'
|
|
1838
1893
|
&&Boolean(this.model)
|
|
1839
1894
|
&&Boolean(this.license);
|
|
1840
1895
|
}
|
|
@@ -1842,21 +1897,21 @@ class AI {
|
|
|
1842
1897
|
#assertServiceConfigured(service=this.llmService,role='llm'){
|
|
1843
1898
|
if(this.#usesProviderRuntime(role,service)){
|
|
1844
1899
|
const internal=role==='llm'
|
|
1845
|
-
?this.#
|
|
1846
|
-
:this.#
|
|
1900
|
+
?this.#builtInLLMSelection(false)
|
|
1901
|
+
:this.#builtInSpeechSelection(role,false);
|
|
1847
1902
|
const internalAvailable=!internal
|
|
1848
1903
|
||(role==='llm'
|
|
1849
|
-
?this.#
|
|
1850
|
-
:this.#
|
|
1904
|
+
?this.#builtInLLMCapability(internal.providerId)
|
|
1905
|
+
:this.#builtInSpeechCapability(role,internal.providerId));
|
|
1851
1906
|
if(!internalAvailable){
|
|
1852
1907
|
const inspection=role==='llm'
|
|
1853
|
-
?this.#
|
|
1854
|
-
:this.#
|
|
1908
|
+
?this.#builtInLLMInspection(internal.providerId,internal)
|
|
1909
|
+
:this.#builtInSpeechInspection(
|
|
1855
1910
|
role,
|
|
1856
1911
|
internal.providerId,
|
|
1857
1912
|
internal
|
|
1858
1913
|
);
|
|
1859
|
-
throw
|
|
1914
|
+
throw aiProviderError(
|
|
1860
1915
|
inspection.message,
|
|
1861
1916
|
inspection.code
|
|
1862
1917
|
);
|
|
@@ -1886,7 +1941,7 @@ class AI {
|
|
|
1886
1941
|
throw error;
|
|
1887
1942
|
}
|
|
1888
1943
|
|
|
1889
|
-
if(service==='
|
|
1944
|
+
if(service==='TWIN'&&role==='llm'&&Boolean(this.twinKey)){
|
|
1890
1945
|
return true;
|
|
1891
1946
|
}
|
|
1892
1947
|
|
|
@@ -1900,9 +1955,9 @@ class AI {
|
|
|
1900
1955
|
}
|
|
1901
1956
|
|
|
1902
1957
|
#shouldUseProviderRuntime(role,service,localOnly=false){
|
|
1903
|
-
//
|
|
1958
|
+
// Built-in adapters publish lifecycle without replacing established
|
|
1904
1959
|
// public transport callbacks or their cancellation behavior.
|
|
1905
|
-
if(role==='llm'&&this.#
|
|
1960
|
+
if(role==='llm'&&this.#builtInLLMSelection(localOnly)){
|
|
1906
1961
|
return false;
|
|
1907
1962
|
}
|
|
1908
1963
|
if(!localOnly){
|
|
@@ -1940,7 +1995,9 @@ class AI {
|
|
|
1940
1995
|
speechPlaybackStarting=false;
|
|
1941
1996
|
speechResumeAttempt=0;
|
|
1942
1997
|
speechResumePending=false;
|
|
1943
|
-
|
|
1998
|
+
speechScheduleGeneration=0;
|
|
1999
|
+
speechScheduleContext=null;
|
|
2000
|
+
speechScheduleTime=0;
|
|
1944
2001
|
speechUnlockHandler=null;
|
|
1945
2002
|
|
|
1946
2003
|
#nextPreferenceTuple(values){
|
|
@@ -1963,7 +2020,7 @@ class AI {
|
|
|
1963
2020
|
|
|
1964
2021
|
#assertValidProviderTuple(tuple){
|
|
1965
2022
|
if(tuple[0]==='OLLAMA'){
|
|
1966
|
-
const mappedModel=tuple[3]==='
|
|
2023
|
+
const mappedModel=tuple[3]==='TWIN'?null:this.#models[tuple[3]];
|
|
1967
2024
|
if(!mappedModel&&!normalizeOllamaModelIdentifier(tuple[3])){
|
|
1968
2025
|
const error=new TypeError('The Ollama model preference is invalid.');
|
|
1969
2026
|
error.code='AI_MODEL_INVALID';
|
|
@@ -1994,13 +2051,13 @@ class AI {
|
|
|
1994
2051
|
|
|
1995
2052
|
#normalizedLLMModel(service,model){
|
|
1996
2053
|
if(service==='OLLAMA'){
|
|
1997
|
-
const mappedModel=model==='
|
|
2054
|
+
const mappedModel=model==='TWIN'?null:this.#models[model];
|
|
1998
2055
|
return mappedModel
|
|
1999
2056
|
||normalizeOllamaModelIdentifier(model)
|
|
2000
2057
|
||model;
|
|
2001
2058
|
}
|
|
2002
|
-
if(service==='
|
|
2003
|
-
return this.#models.
|
|
2059
|
+
if(service==='TWIN'){
|
|
2060
|
+
return model==='TWIN'?this.#models.TWIN:model;
|
|
2004
2061
|
}
|
|
2005
2062
|
return model;
|
|
2006
2063
|
}
|
|
@@ -2079,12 +2136,12 @@ class AI {
|
|
|
2079
2136
|
const identity=providerId&&modelId
|
|
2080
2137
|
?this.#providerRuntime.providerIdentity(role,providerId)
|
|
2081
2138
|
:null;
|
|
2082
|
-
const
|
|
2139
|
+
const pendingExternalRoute=Boolean(
|
|
2083
2140
|
providerId
|
|
2084
2141
|
&&modelId
|
|
2085
|
-
&&!
|
|
2142
|
+
&&!BUILT_IN_AI_SERVICES.has(providerId)
|
|
2086
2143
|
);
|
|
2087
|
-
if(!identity&&!
|
|
2144
|
+
if(!identity&&!pendingExternalRoute){
|
|
2088
2145
|
selections[role]={default:null,localOnly:null};
|
|
2089
2146
|
continue;
|
|
2090
2147
|
}
|
|
@@ -2103,15 +2160,15 @@ class AI {
|
|
|
2103
2160
|
return selections;
|
|
2104
2161
|
}
|
|
2105
2162
|
|
|
2106
|
-
#
|
|
2163
|
+
#assertRegisteredBuiltInRoutes(selections){
|
|
2107
2164
|
for(const role of ['llm','stt','tts']){
|
|
2108
2165
|
for(const routeName of ['default','localOnly']){
|
|
2109
2166
|
const selection=selections?.[role]?.[routeName];
|
|
2110
2167
|
if(selection
|
|
2111
|
-
&&
|
|
2168
|
+
&&BUILT_IN_AI_SERVICES.has(selection.providerId)
|
|
2112
2169
|
&&!this.#providerRuntime.hasProvider(role,selection.providerId)){
|
|
2113
2170
|
const error=new Error(
|
|
2114
|
-
`
|
|
2171
|
+
`Built-in AI provider ${selection.providerId} requires an explicit ${role} adapter before routing.`
|
|
2115
2172
|
);
|
|
2116
2173
|
error.code='ARCANE_AI_PROVIDER_UNAVAILABLE';
|
|
2117
2174
|
throw error;
|
|
@@ -2196,22 +2253,22 @@ class AI {
|
|
|
2196
2253
|
]);
|
|
2197
2254
|
this.#assertValidProviderTuple(tuple);
|
|
2198
2255
|
this.#assertSynchronousBrowserSpeechSupersession('AI.setAI');
|
|
2199
|
-
this.#
|
|
2200
|
-
this.#
|
|
2201
|
-
this.#
|
|
2256
|
+
this.#ensureBuiltInLLMProvider(tuple[0]);
|
|
2257
|
+
this.#ensureBuiltInSpeechProvider('stt',tuple[1]);
|
|
2258
|
+
this.#ensureBuiltInSpeechProvider('tts',tuple[2]);
|
|
2202
2259
|
this.#providerRuntime.configure(this.#routesFromPreferenceTuple(tuple));
|
|
2203
2260
|
this.#invalidateSpeechControl();
|
|
2204
2261
|
this.#applyPreferenceTuple(tuple);
|
|
2205
|
-
this.#
|
|
2206
|
-
this.#
|
|
2262
|
+
this.#releaseInactiveBuiltInLLMProviders(tuple[0]);
|
|
2263
|
+
this.#releaseInactiveBuiltInSpeechProviders({
|
|
2207
2264
|
stt:tuple[1],
|
|
2208
2265
|
tts:tuple[2]
|
|
2209
2266
|
});
|
|
2210
|
-
this.#
|
|
2211
|
-
this.#
|
|
2267
|
+
this.#retainBuiltInLLMReadiness(
|
|
2268
|
+
this.#reconcileBuiltInLLMReadiness()
|
|
2212
2269
|
);
|
|
2213
|
-
this.#
|
|
2214
|
-
this.#
|
|
2270
|
+
this.#retainBuiltInSpeechReadiness(
|
|
2271
|
+
this.#reconcileBuiltInSpeechReadiness()
|
|
2215
2272
|
);
|
|
2216
2273
|
return true;
|
|
2217
2274
|
}
|
|
@@ -2220,33 +2277,33 @@ class AI {
|
|
|
2220
2277
|
const prepared=this.#providerRuntime.validateConfiguration(selections);
|
|
2221
2278
|
this.#assertDeviceSpeechConfiguration(prepared);
|
|
2222
2279
|
this.#assertSynchronousBrowserSpeechSupersession('AI.configureProviders');
|
|
2223
|
-
this.#
|
|
2280
|
+
this.#ensureBuiltInLLMProvider(
|
|
2224
2281
|
prepared.llm.default?.providerId
|
|
2225
2282
|
);
|
|
2226
|
-
this.#
|
|
2283
|
+
this.#ensureBuiltInSpeechProvider(
|
|
2227
2284
|
'stt',
|
|
2228
2285
|
prepared.stt.default?.providerId
|
|
2229
2286
|
);
|
|
2230
|
-
this.#
|
|
2287
|
+
this.#ensureBuiltInSpeechProvider(
|
|
2231
2288
|
'tts',
|
|
2232
2289
|
prepared.tts.default?.providerId
|
|
2233
2290
|
);
|
|
2234
|
-
this.#
|
|
2291
|
+
this.#assertRegisteredBuiltInRoutes(prepared);
|
|
2235
2292
|
const configured=this.#providerRuntime.configure(prepared);
|
|
2236
2293
|
this.#invalidateSpeechControl();
|
|
2237
2294
|
this.#applyPreferenceTuple(this.#tupleFromProviderRoutes(configured));
|
|
2238
|
-
this.#
|
|
2295
|
+
this.#releaseInactiveBuiltInLLMProviders(
|
|
2239
2296
|
configured.llm.default?.providerId
|
|
2240
2297
|
);
|
|
2241
|
-
this.#
|
|
2298
|
+
this.#releaseInactiveBuiltInSpeechProviders({
|
|
2242
2299
|
stt:configured.stt.default?.providerId,
|
|
2243
2300
|
tts:configured.tts.default?.providerId
|
|
2244
2301
|
});
|
|
2245
|
-
this.#
|
|
2246
|
-
this.#
|
|
2302
|
+
this.#retainBuiltInLLMReadiness(
|
|
2303
|
+
this.#reconcileBuiltInLLMReadiness()
|
|
2247
2304
|
);
|
|
2248
|
-
this.#
|
|
2249
|
-
this.#
|
|
2305
|
+
this.#retainBuiltInSpeechReadiness(
|
|
2306
|
+
this.#reconcileBuiltInSpeechReadiness()
|
|
2250
2307
|
);
|
|
2251
2308
|
return configured;
|
|
2252
2309
|
}
|
|
@@ -2257,26 +2314,26 @@ class AI {
|
|
|
2257
2314
|
this.#assertSynchronousBrowserSpeechSupersession(
|
|
2258
2315
|
'AI.configureSpeechProviders'
|
|
2259
2316
|
);
|
|
2260
|
-
this.#
|
|
2317
|
+
this.#ensureBuiltInSpeechProvider(
|
|
2261
2318
|
'stt',
|
|
2262
2319
|
prepared.stt.default?.providerId
|
|
2263
2320
|
);
|
|
2264
|
-
this.#
|
|
2321
|
+
this.#ensureBuiltInSpeechProvider(
|
|
2265
2322
|
'tts',
|
|
2266
2323
|
prepared.tts.default?.providerId
|
|
2267
2324
|
);
|
|
2268
|
-
this.#
|
|
2325
|
+
this.#assertRegisteredBuiltInRoutes(prepared);
|
|
2269
2326
|
const configured=this.#providerRuntime.configureSpeech(prepared);
|
|
2270
2327
|
this.#invalidateSpeechControl();
|
|
2271
2328
|
this.#applySpeechPreferenceTuple(
|
|
2272
2329
|
this.#tupleFromSpeechProviderRoutes(configured)
|
|
2273
2330
|
);
|
|
2274
|
-
this.#
|
|
2331
|
+
this.#releaseInactiveBuiltInSpeechProviders({
|
|
2275
2332
|
stt:configured.stt.default?.providerId,
|
|
2276
2333
|
tts:configured.tts.default?.providerId
|
|
2277
2334
|
});
|
|
2278
|
-
this.#
|
|
2279
|
-
this.#
|
|
2335
|
+
this.#retainBuiltInSpeechReadiness(
|
|
2336
|
+
this.#reconcileBuiltInSpeechReadiness()
|
|
2280
2337
|
);
|
|
2281
2338
|
this.muted=true;
|
|
2282
2339
|
this.stopAudio();
|
|
@@ -2303,18 +2360,18 @@ class AI {
|
|
|
2303
2360
|
await this.#supersedeBrowserSpeechForRouteChange();
|
|
2304
2361
|
this.#invalidateSpeechControl();
|
|
2305
2362
|
await this.#unloadProviderRolesForTransition();
|
|
2306
|
-
this.#
|
|
2307
|
-
this.#
|
|
2308
|
-
this.#
|
|
2363
|
+
this.#ensureBuiltInLLMProvider(tuple[0]);
|
|
2364
|
+
this.#ensureBuiltInSpeechProvider('stt',tuple[1]);
|
|
2365
|
+
this.#ensureBuiltInSpeechProvider('tts',tuple[2]);
|
|
2309
2366
|
this.#providerRuntime.configure(this.#routesFromPreferenceTuple(tuple));
|
|
2310
2367
|
this.#applyPreferenceTuple(tuple);
|
|
2311
|
-
this.#
|
|
2312
|
-
this.#
|
|
2368
|
+
this.#releaseInactiveBuiltInLLMProviders(tuple[0]);
|
|
2369
|
+
this.#releaseInactiveBuiltInSpeechProviders({
|
|
2313
2370
|
stt:tuple[1],
|
|
2314
2371
|
tts:tuple[2]
|
|
2315
2372
|
});
|
|
2316
|
-
await this.#
|
|
2317
|
-
await this.#
|
|
2373
|
+
await this.#reconcileBuiltInLLMReadiness();
|
|
2374
|
+
await this.#reconcileBuiltInSpeechReadiness();
|
|
2318
2375
|
return this.#providerRuntime.status();
|
|
2319
2376
|
}
|
|
2320
2377
|
|
|
@@ -2322,31 +2379,31 @@ class AI {
|
|
|
2322
2379
|
const prepared=this.#providerRuntime.validateConfiguration(selections);
|
|
2323
2380
|
this.#assertDeviceSpeechConfiguration(prepared);
|
|
2324
2381
|
await this.#supersedeBrowserSpeechForRouteChange();
|
|
2325
|
-
this.#
|
|
2382
|
+
this.#ensureBuiltInLLMProvider(
|
|
2326
2383
|
prepared.llm.default?.providerId
|
|
2327
2384
|
);
|
|
2328
|
-
this.#
|
|
2385
|
+
this.#ensureBuiltInSpeechProvider(
|
|
2329
2386
|
'stt',
|
|
2330
2387
|
prepared.stt.default?.providerId
|
|
2331
2388
|
);
|
|
2332
|
-
this.#
|
|
2389
|
+
this.#ensureBuiltInSpeechProvider(
|
|
2333
2390
|
'tts',
|
|
2334
2391
|
prepared.tts.default?.providerId
|
|
2335
2392
|
);
|
|
2336
|
-
this.#
|
|
2393
|
+
this.#assertRegisteredBuiltInRoutes(prepared);
|
|
2337
2394
|
this.#invalidateSpeechControl();
|
|
2338
2395
|
await this.#unloadProviderRolesForTransition();
|
|
2339
2396
|
const configured=this.#providerRuntime.configure(prepared);
|
|
2340
2397
|
this.#applyPreferenceTuple(this.#tupleFromProviderRoutes(configured));
|
|
2341
|
-
this.#
|
|
2398
|
+
this.#releaseInactiveBuiltInLLMProviders(
|
|
2342
2399
|
configured.llm.default?.providerId
|
|
2343
2400
|
);
|
|
2344
|
-
this.#
|
|
2401
|
+
this.#releaseInactiveBuiltInSpeechProviders({
|
|
2345
2402
|
stt:configured.stt.default?.providerId,
|
|
2346
2403
|
tts:configured.tts.default?.providerId
|
|
2347
2404
|
});
|
|
2348
|
-
await this.#
|
|
2349
|
-
await this.#
|
|
2405
|
+
await this.#reconcileBuiltInLLMReadiness();
|
|
2406
|
+
await this.#reconcileBuiltInSpeechReadiness();
|
|
2350
2407
|
return configured;
|
|
2351
2408
|
}
|
|
2352
2409
|
|
|
@@ -2356,24 +2413,24 @@ class AI {
|
|
|
2356
2413
|
await this.#supersedeBrowserSpeechForRouteChange();
|
|
2357
2414
|
this.#invalidateSpeechControl();
|
|
2358
2415
|
await this.#unloadSpeechProviderRolesForTransition();
|
|
2359
|
-
this.#
|
|
2416
|
+
this.#ensureBuiltInSpeechProvider(
|
|
2360
2417
|
'stt',
|
|
2361
2418
|
prepared.stt.default?.providerId
|
|
2362
2419
|
);
|
|
2363
|
-
this.#
|
|
2420
|
+
this.#ensureBuiltInSpeechProvider(
|
|
2364
2421
|
'tts',
|
|
2365
2422
|
prepared.tts.default?.providerId
|
|
2366
2423
|
);
|
|
2367
|
-
this.#
|
|
2424
|
+
this.#assertRegisteredBuiltInRoutes(prepared);
|
|
2368
2425
|
const configured=this.#providerRuntime.configureSpeech(prepared);
|
|
2369
2426
|
this.#applySpeechPreferenceTuple(
|
|
2370
2427
|
this.#tupleFromSpeechProviderRoutes(configured)
|
|
2371
2428
|
);
|
|
2372
|
-
this.#
|
|
2429
|
+
this.#releaseInactiveBuiltInSpeechProviders({
|
|
2373
2430
|
stt:configured.stt.default?.providerId,
|
|
2374
2431
|
tts:configured.tts.default?.providerId
|
|
2375
2432
|
});
|
|
2376
|
-
await this.#
|
|
2433
|
+
await this.#reconcileBuiltInSpeechReadiness();
|
|
2377
2434
|
this.muted=true;
|
|
2378
2435
|
return configured;
|
|
2379
2436
|
}
|
|
@@ -2484,7 +2541,7 @@ class AI {
|
|
|
2484
2541
|
|
|
2485
2542
|
#browserSpeechReplacementBoundary(previousRecord,roles){
|
|
2486
2543
|
const expectedProviders={stt:null,tts:null};
|
|
2487
|
-
const
|
|
2544
|
+
const builtInRecords=[];
|
|
2488
2545
|
for(const role of roles){
|
|
2489
2546
|
if(previousRecord?.managedRoles.includes(role)){
|
|
2490
2547
|
expectedProviders[role]=previousRecord.providers[role];
|
|
@@ -2495,8 +2552,8 @@ class AI {
|
|
|
2495
2552
|
expectedProviders[role]=null;
|
|
2496
2553
|
continue;
|
|
2497
2554
|
}
|
|
2498
|
-
const record=this.#
|
|
2499
|
-
this.#
|
|
2555
|
+
const record=this.#builtInSpeechProviders.get(
|
|
2556
|
+
this.#builtInSpeechProviderKey(role,selection.providerId)
|
|
2500
2557
|
);
|
|
2501
2558
|
if(!record
|
|
2502
2559
|
||!this.#providerRuntime.ownsProvider(role,record.provider)){
|
|
@@ -2509,41 +2566,41 @@ class AI {
|
|
|
2509
2566
|
continue;
|
|
2510
2567
|
}
|
|
2511
2568
|
throw this.#browserSpeechProviderRouteOwnershipError(
|
|
2512
|
-
`The selected ${role} route is not owned by the replaceable AI
|
|
2569
|
+
`The selected ${role} route is not owned by the replaceable built-in AI speech boundary.`
|
|
2513
2570
|
);
|
|
2514
2571
|
}
|
|
2515
2572
|
expectedProviders[role]=record.provider;
|
|
2516
|
-
|
|
2573
|
+
builtInRecords.push(record);
|
|
2517
2574
|
}
|
|
2518
2575
|
return completeValue({
|
|
2519
2576
|
expectedProviders:completeValue(expectedProviders),
|
|
2520
|
-
|
|
2577
|
+
builtInRecords:completeValue(builtInRecords)
|
|
2521
2578
|
});
|
|
2522
2579
|
}
|
|
2523
2580
|
|
|
2524
|
-
async #
|
|
2581
|
+
async #cleanupRetiredBuiltInSpeechProviders(
|
|
2525
2582
|
{signal=null,committed=false}={}
|
|
2526
2583
|
){
|
|
2527
2584
|
const failures=[];
|
|
2528
|
-
for(const record of [...this.#
|
|
2585
|
+
for(const record of [...this.#browserSpeechRetiredBuiltInRecords]){
|
|
2529
2586
|
try{
|
|
2530
2587
|
if(this.#providerRuntime.ownsProvider(
|
|
2531
2588
|
record.role,
|
|
2532
2589
|
record.provider
|
|
2533
2590
|
)){
|
|
2534
2591
|
throw this.#browserSpeechProviderRouteOwnershipError(
|
|
2535
|
-
`The retired ${record.role}
|
|
2592
|
+
`The retired ${record.role} built-in speech provider still owns its registry entry.`
|
|
2536
2593
|
);
|
|
2537
2594
|
}
|
|
2538
2595
|
await record.provider.dispose({role:record.role,signal});
|
|
2539
|
-
const key=this.#
|
|
2596
|
+
const key=this.#builtInSpeechProviderKey(
|
|
2540
2597
|
record.role,
|
|
2541
2598
|
record.providerId
|
|
2542
2599
|
);
|
|
2543
|
-
if(this.#
|
|
2544
|
-
this.#
|
|
2600
|
+
if(this.#builtInSpeechProviders.get(key)===record){
|
|
2601
|
+
this.#builtInSpeechProviders.delete(key);
|
|
2545
2602
|
}
|
|
2546
|
-
this.#
|
|
2603
|
+
this.#browserSpeechRetiredBuiltInRecords.delete(record);
|
|
2547
2604
|
}catch(error){
|
|
2548
2605
|
failures.push(error);
|
|
2549
2606
|
}
|
|
@@ -2552,12 +2609,12 @@ class AI {
|
|
|
2552
2609
|
throw aiBrowserSpeechError(
|
|
2553
2610
|
AI_BROWSER_SPEECH_ERROR_CODES.providerDisposalRejected,
|
|
2554
2611
|
AI_BROWSER_SPEECH_REASONS.providerDisposalRejected,
|
|
2555
|
-
'The replaced
|
|
2612
|
+
'The replaced built-in speech providers could not be disposed.',
|
|
2556
2613
|
failures.length===1
|
|
2557
2614
|
?failures[0]
|
|
2558
2615
|
:new AggregateError(
|
|
2559
2616
|
failures,
|
|
2560
|
-
'Multiple replaced
|
|
2617
|
+
'Multiple replaced built-in speech provider disposals were rejected.'
|
|
2561
2618
|
),
|
|
2562
2619
|
{committed}
|
|
2563
2620
|
);
|
|
@@ -2655,7 +2712,12 @@ class AI {
|
|
|
2655
2712
|
?{artifactGraphId:catalog.artifactGraphId}
|
|
2656
2713
|
:{}),
|
|
2657
2714
|
offline:configured.offline,
|
|
2658
|
-
...(role==='tts'
|
|
2715
|
+
...(role==='tts'
|
|
2716
|
+
?{
|
|
2717
|
+
defaultVoice:catalog.defaultVoice,
|
|
2718
|
+
execution:configured.execution
|
|
2719
|
+
}
|
|
2720
|
+
:{})
|
|
2659
2721
|
});
|
|
2660
2722
|
}
|
|
2661
2723
|
const roles={};
|
|
@@ -3024,7 +3086,8 @@ class AI {
|
|
|
3024
3086
|
runtime:configured.runtime
|
|
3025
3087
|
}),
|
|
3026
3088
|
store,
|
|
3027
|
-
offline:configured.offline
|
|
3089
|
+
offline:configured.offline,
|
|
3090
|
+
...(role==='tts'?{execution:configured.execution}:{})
|
|
3028
3091
|
});
|
|
3029
3092
|
}
|
|
3030
3093
|
providers=completeValue({...candidateProviders});
|
|
@@ -3160,8 +3223,8 @@ class AI {
|
|
|
3160
3223
|
candidate,
|
|
3161
3224
|
replacement
|
|
3162
3225
|
);
|
|
3163
|
-
for(const
|
|
3164
|
-
this.#
|
|
3226
|
+
for(const builtInRecord of replacementBoundary.builtInRecords){
|
|
3227
|
+
this.#browserSpeechRetiredBuiltInRecords.add(builtInRecord);
|
|
3165
3228
|
}
|
|
3166
3229
|
if(previousRecord){
|
|
3167
3230
|
this.#retireBrowserSpeechRegistration(
|
|
@@ -3257,7 +3320,7 @@ class AI {
|
|
|
3257
3320
|
generation,
|
|
3258
3321
|
{committed:true}
|
|
3259
3322
|
);
|
|
3260
|
-
await this.#
|
|
3323
|
+
await this.#cleanupRetiredBuiltInSpeechProviders({
|
|
3261
3324
|
signal:controller.signal,
|
|
3262
3325
|
committed:true
|
|
3263
3326
|
});
|
|
@@ -3423,7 +3486,7 @@ class AI {
|
|
|
3423
3486
|
record.managedRoles.includes('tts')
|
|
3424
3487
|
||record.candidateRoles.includes('tts')
|
|
3425
3488
|
)
|
|
3426
|
-
||[...this.#
|
|
3489
|
+
||[...this.#browserSpeechRetiredBuiltInRecords].some(
|
|
3427
3490
|
record=>record.role==='tts'
|
|
3428
3491
|
)
|
|
3429
3492
|
);
|
|
@@ -3451,7 +3514,7 @@ class AI {
|
|
|
3451
3514
|
if(activeRecord)records.add(activeRecord);
|
|
3452
3515
|
const eventRecord=activeRecord||records.values().next().value||null;
|
|
3453
3516
|
if(!eventRecord
|
|
3454
|
-
&&runtime.#
|
|
3517
|
+
&&runtime.#browserSpeechRetiredBuiltInRecords.size===0){
|
|
3455
3518
|
return false;
|
|
3456
3519
|
}
|
|
3457
3520
|
if(eventRecord){
|
|
@@ -3535,8 +3598,8 @@ class AI {
|
|
|
3535
3598
|
);
|
|
3536
3599
|
changed=true;
|
|
3537
3600
|
}
|
|
3538
|
-
if(runtime.#
|
|
3539
|
-
await runtime.#
|
|
3601
|
+
if(runtime.#browserSpeechRetiredBuiltInRecords.size){
|
|
3602
|
+
await runtime.#cleanupRetiredBuiltInSpeechProviders({
|
|
3540
3603
|
signal:controller.signal,
|
|
3541
3604
|
committed:true
|
|
3542
3605
|
});
|
|
@@ -3707,7 +3770,7 @@ class AI {
|
|
|
3707
3770
|
return null;
|
|
3708
3771
|
}
|
|
3709
3772
|
|
|
3710
|
-
async #
|
|
3773
|
+
async #requestBuiltInSpeechTranscription(payload={},signal=null){
|
|
3711
3774
|
const audio=payload?.audio;
|
|
3712
3775
|
if(!audio||typeof audio.arrayBuffer!=='function'){
|
|
3713
3776
|
throw new TypeError('Speech transcription requires an audio Blob or File.');
|
|
@@ -3758,7 +3821,7 @@ class AI {
|
|
|
3758
3821
|
return response.text();
|
|
3759
3822
|
}
|
|
3760
3823
|
|
|
3761
|
-
async #
|
|
3824
|
+
async #requestBuiltInSpeechSynthesis(payload={},signal=null){
|
|
3762
3825
|
const input=typeof payload?.input==='string'?payload.input:'';
|
|
3763
3826
|
if(!input){
|
|
3764
3827
|
throw new TypeError('Speech synthesis requires nonempty input.');
|
|
@@ -3769,7 +3832,7 @@ class AI {
|
|
|
3769
3832
|
const model=String(payload.model||this.modelTTS);
|
|
3770
3833
|
const voice=typeof payload.voice==='string'&&payload.voice.trim()
|
|
3771
3834
|
?payload.voice.trim()
|
|
3772
|
-
:this.#
|
|
3835
|
+
:this.#builtInSpeechDefaultVoice('tts',this.ttsService);
|
|
3773
3836
|
if(!voice){
|
|
3774
3837
|
throw new TypeError('The selected speech provider requires a voice.');
|
|
3775
3838
|
}
|
|
@@ -3827,14 +3890,14 @@ class AI {
|
|
|
3827
3890
|
if(isAIRequestAbort(error,signal)){
|
|
3828
3891
|
throw normalizeAIRequestAbort(error);
|
|
3829
3892
|
}
|
|
3830
|
-
throw
|
|
3893
|
+
throw aiProviderError(
|
|
3831
3894
|
'The configured TTS HTTP request failed.',
|
|
3832
3895
|
'ARCANE_AI_TTS_HTTP_REQUEST_FAILED',
|
|
3833
3896
|
error
|
|
3834
3897
|
);
|
|
3835
3898
|
}
|
|
3836
3899
|
if(!response.ok){
|
|
3837
|
-
const error=
|
|
3900
|
+
const error=aiProviderError(
|
|
3838
3901
|
`The configured TTS HTTP response was rejected with status ${response.status}.`,
|
|
3839
3902
|
'ARCANE_AI_TTS_HTTP_RESPONSE_REJECTED'
|
|
3840
3903
|
);
|
|
@@ -4113,47 +4176,50 @@ class AI {
|
|
|
4113
4176
|
return typeof content==='string'?content:completion;
|
|
4114
4177
|
}
|
|
4115
4178
|
|
|
4116
|
-
#
|
|
4179
|
+
#requestBuiltInLLMChat(payload={},signal=null){
|
|
4117
4180
|
const parallelToolCalls=payload.parallelToolCalls!==undefined
|
|
4118
4181
|
?payload.parallelToolCalls
|
|
4119
4182
|
:payload.parallel_tool_calls;
|
|
4120
|
-
return this.#
|
|
4183
|
+
return this.#fetchBuiltIn(
|
|
4121
4184
|
payload.messages??[],
|
|
4122
|
-
function
|
|
4185
|
+
function ignoreBuiltInLLMProviderResponse(){},
|
|
4123
4186
|
payload.structuredOutput??false,
|
|
4124
4187
|
payload.tools??[],
|
|
4125
4188
|
payload.toolChoice??'auto',
|
|
4126
4189
|
parallelToolCalls,
|
|
4127
4190
|
payload.id??Date.now(),
|
|
4128
|
-
function
|
|
4129
|
-
signal
|
|
4191
|
+
function ignoreBuiltInLLMProviderRequest(){},
|
|
4192
|
+
signal,
|
|
4193
|
+
payload.reasoningEffort
|
|
4130
4194
|
);
|
|
4131
4195
|
}
|
|
4132
4196
|
|
|
4133
|
-
#
|
|
4197
|
+
#requestBuiltInLLMStream(payload={},bridge){
|
|
4134
4198
|
const parallelToolCalls=payload.parallelToolCalls!==undefined
|
|
4135
4199
|
?payload.parallelToolCalls
|
|
4136
4200
|
:payload.parallel_tool_calls;
|
|
4137
|
-
function
|
|
4201
|
+
function emitBuiltInLLMStreamData(chunk){
|
|
4138
4202
|
bridge.emit(chunk);
|
|
4139
4203
|
}
|
|
4140
4204
|
|
|
4141
|
-
return this.#
|
|
4205
|
+
return this.#streamBuiltInMessage(
|
|
4142
4206
|
payload.messages??[],
|
|
4143
|
-
function
|
|
4144
|
-
function
|
|
4207
|
+
function ignoreBuiltInLLMScalarStream(){},
|
|
4208
|
+
function ignoreBuiltInLLMProviderCompletion(){},
|
|
4145
4209
|
payload.tools??[],
|
|
4146
4210
|
payload.toolChoice??'auto',
|
|
4147
|
-
function
|
|
4211
|
+
function retainBuiltInLLMStreamToolUntilCompletion(){},
|
|
4148
4212
|
parallelToolCalls,
|
|
4149
4213
|
payload.id??Date.now(),
|
|
4150
4214
|
payload.seeThinking??false,
|
|
4151
4215
|
bridge.signal,
|
|
4152
|
-
function
|
|
4216
|
+
function ignoreBuiltInLLMProviderRequest(){},
|
|
4153
4217
|
payload.structuredOutput??false,
|
|
4154
4218
|
false,
|
|
4155
4219
|
true,
|
|
4156
|
-
|
|
4220
|
+
emitBuiltInLLMStreamData,
|
|
4221
|
+
function ignoreBuiltInLLMStreamResult(){},
|
|
4222
|
+
payload.reasoningEffort
|
|
4157
4223
|
);
|
|
4158
4224
|
}
|
|
4159
4225
|
|
|
@@ -4323,9 +4389,13 @@ class AI {
|
|
|
4323
4389
|
minP,
|
|
4324
4390
|
seed,
|
|
4325
4391
|
stop,
|
|
4326
|
-
templateOptions
|
|
4392
|
+
templateOptions,
|
|
4393
|
+
reasoningEffort
|
|
4327
4394
|
}={}){
|
|
4328
4395
|
validateAIStructuralRequest(messages,tools,parallelToolCalls);
|
|
4396
|
+
const normalizedReasoningEffort=normalizeAIReasoningEffort(
|
|
4397
|
+
reasoningEffort===undefined?this.reasoningEffort:reasoningEffort
|
|
4398
|
+
);
|
|
4329
4399
|
if(localOnly!==true&&localOnly!==false){
|
|
4330
4400
|
throw new TypeError('AI localOnly must be a boolean.');
|
|
4331
4401
|
}
|
|
@@ -4355,7 +4425,10 @@ class AI {
|
|
|
4355
4425
|
...(minP!==undefined?{minP}:{}),
|
|
4356
4426
|
...(seed!==undefined?{seed}:{}),
|
|
4357
4427
|
...(stop!==undefined?{stop}:{}),
|
|
4358
|
-
...(templateOptions!==undefined?{templateOptions}:{})
|
|
4428
|
+
...(templateOptions!==undefined?{templateOptions}:{}),
|
|
4429
|
+
...(normalizedReasoningEffort
|
|
4430
|
+
?{reasoningEffort:normalizedReasoningEffort}
|
|
4431
|
+
:{})
|
|
4359
4432
|
};
|
|
4360
4433
|
const displayId=`M-${id}`;
|
|
4361
4434
|
let handle=null;
|
|
@@ -4441,13 +4514,13 @@ class AI {
|
|
|
4441
4514
|
}
|
|
4442
4515
|
|
|
4443
4516
|
try{
|
|
4444
|
-
const completion=await this.#
|
|
4517
|
+
const completion=await this.#streamBuiltInMessage(
|
|
4445
4518
|
messages,
|
|
4446
4519
|
onChunk,
|
|
4447
|
-
function
|
|
4520
|
+
function retainBuiltInCompletionUntilResponse(){},
|
|
4448
4521
|
tools,
|
|
4449
4522
|
toolChoice,
|
|
4450
|
-
function
|
|
4523
|
+
function retainBuiltInToolCallUntilResponse(){},
|
|
4451
4524
|
parallelToolCalls,
|
|
4452
4525
|
id,
|
|
4453
4526
|
seeThinking,
|
|
@@ -4457,7 +4530,8 @@ class AI {
|
|
|
4457
4530
|
false,
|
|
4458
4531
|
true,
|
|
4459
4532
|
onDataChunk,
|
|
4460
|
-
onDataResult
|
|
4533
|
+
onDataResult,
|
|
4534
|
+
normalizedReasoningEffort
|
|
4461
4535
|
);
|
|
4462
4536
|
const structuralToolCalls=normalizeAICompletionToolCalls(
|
|
4463
4537
|
completion
|
|
@@ -4527,7 +4601,7 @@ class AI {
|
|
|
4527
4601
|
});
|
|
4528
4602
|
}
|
|
4529
4603
|
|
|
4530
|
-
return this.#
|
|
4604
|
+
return this.#streamBuiltInMessage(
|
|
4531
4605
|
messages,
|
|
4532
4606
|
streamHandler,
|
|
4533
4607
|
streamComplete,
|
|
@@ -4547,7 +4621,7 @@ class AI {
|
|
|
4547
4621
|
);
|
|
4548
4622
|
}
|
|
4549
4623
|
|
|
4550
|
-
async #
|
|
4624
|
+
async #streamBuiltInMessage(
|
|
4551
4625
|
messages=[],
|
|
4552
4626
|
streamHandler=function ignoreStreamChunk(){},
|
|
4553
4627
|
streamComplete=function finishIgnoredStream(){},
|
|
@@ -4562,8 +4636,9 @@ class AI {
|
|
|
4562
4636
|
structuredOutput=false,
|
|
4563
4637
|
finishSpeech=true,
|
|
4564
4638
|
returnCompletion=false,
|
|
4565
|
-
dataChunkHandler=function
|
|
4566
|
-
dataResultHandler=function
|
|
4639
|
+
dataChunkHandler=function ignoreBuiltInStreamDataChunk(){},
|
|
4640
|
+
dataResultHandler=function ignoreBuiltInStreamDataResult(){},
|
|
4641
|
+
reasoningEffort
|
|
4567
4642
|
){
|
|
4568
4643
|
let speechTurnCompleted=false;
|
|
4569
4644
|
|
|
@@ -4583,6 +4658,9 @@ class AI {
|
|
|
4583
4658
|
structuredOutput
|
|
4584
4659
|
);
|
|
4585
4660
|
|
|
4661
|
+
const normalizedReasoningEffort=normalizeAIReasoningEffort(
|
|
4662
|
+
reasoningEffort===undefined?this.reasoningEffort:reasoningEffort
|
|
4663
|
+
);
|
|
4586
4664
|
const request={
|
|
4587
4665
|
model:this.model,
|
|
4588
4666
|
messages:messages,
|
|
@@ -4603,8 +4681,11 @@ class AI {
|
|
|
4603
4681
|
}
|
|
4604
4682
|
}
|
|
4605
4683
|
|
|
4606
|
-
if(
|
|
4607
|
-
|
|
4684
|
+
if(
|
|
4685
|
+
normalizedReasoningEffort
|
|
4686
|
+
&&(this.llmService==='TWIN'||this.llmService==='OLLAMA')
|
|
4687
|
+
){
|
|
4688
|
+
request.reasoning_effort=normalizedReasoningEffort;
|
|
4608
4689
|
}
|
|
4609
4690
|
|
|
4610
4691
|
let isThinking=true;
|
|
@@ -4615,7 +4696,7 @@ class AI {
|
|
|
4615
4696
|
const nativeOllama=this.#nativeOllama();
|
|
4616
4697
|
|
|
4617
4698
|
if(this.llmService==='OLLAMA'&&!nativeOllama){
|
|
4618
|
-
throw
|
|
4699
|
+
throw aiProviderError(
|
|
4619
4700
|
'Local AI requires the capability-gated Arcane API.',
|
|
4620
4701
|
'AI_NATIVE_LOCAL_REQUIRED'
|
|
4621
4702
|
);
|
|
@@ -4631,7 +4712,9 @@ class AI {
|
|
|
4631
4712
|
model:this.model,
|
|
4632
4713
|
messages:ollamaMessages,
|
|
4633
4714
|
stream:true,
|
|
4634
|
-
...(
|
|
4715
|
+
...(normalizedReasoningEffort
|
|
4716
|
+
?{think:normalizedReasoningEffort}
|
|
4717
|
+
:{}),
|
|
4635
4718
|
...(structuredOutputFormat?{format:structuredOutputFormat}:{}),
|
|
4636
4719
|
...(ollamaTools.length?{tools:ollamaTools}:{})
|
|
4637
4720
|
};
|
|
@@ -5001,7 +5084,6 @@ class AI {
|
|
|
5001
5084
|
const content=typeof choiceDelta.content==='string'
|
|
5002
5085
|
?choiceDelta.content
|
|
5003
5086
|
:'';
|
|
5004
|
-
let value=content;
|
|
5005
5087
|
let reasoning='';
|
|
5006
5088
|
if(seeThinking){
|
|
5007
5089
|
reasoning=typeof choiceDelta.reasoning_content==='string'
|
|
@@ -5011,8 +5093,13 @@ class AI {
|
|
|
5011
5093
|
:'';
|
|
5012
5094
|
}
|
|
5013
5095
|
isThinking=Boolean(reasoning);
|
|
5014
|
-
if(reasoning)
|
|
5015
|
-
|
|
5096
|
+
if(reasoning){
|
|
5097
|
+
await streamHandler(reasoning,`M-${id}`,true);
|
|
5098
|
+
}
|
|
5099
|
+
if(content){
|
|
5100
|
+
isThinking=false;
|
|
5101
|
+
await streamHandler(content,`M-${id}`,false);
|
|
5102
|
+
}
|
|
5016
5103
|
}
|
|
5017
5104
|
}
|
|
5018
5105
|
|
|
@@ -5179,7 +5266,7 @@ class AI {
|
|
|
5179
5266
|
const selectedTerminalChoiceIndex=selectedChoiceIndex??completionChoiceIndexes[0];
|
|
5180
5267
|
const structuralToolCalls=structuralToolCallsByChoice.get(selectedTerminalChoiceIndex)||[];
|
|
5181
5268
|
const completionChoices=completionChoiceIndexes
|
|
5182
|
-
.map(function
|
|
5269
|
+
.map(function completeBuiltInStreamChoice(choicePosition){
|
|
5183
5270
|
const retained=streamedChoicesByIndex.get(choicePosition)||{
|
|
5184
5271
|
choice:{index:choicePosition},
|
|
5185
5272
|
message:{role:'assistant'}
|
|
@@ -5208,7 +5295,7 @@ class AI {
|
|
|
5208
5295
|
});
|
|
5209
5296
|
const completion={
|
|
5210
5297
|
...streamMetadata,
|
|
5211
|
-
id:Object.hasOwn(streamMetadata,'id')?streamMetadata.id:`
|
|
5298
|
+
id:Object.hasOwn(streamMetadata,'id')?streamMetadata.id:`stream-${id}`,
|
|
5212
5299
|
object:Object.hasOwn(streamMetadata,'object')
|
|
5213
5300
|
?streamMetadata.object
|
|
5214
5301
|
:'chat.completion',
|
|
@@ -5222,7 +5309,7 @@ class AI {
|
|
|
5222
5309
|
assertAIStreamToolCallCorrelation(
|
|
5223
5310
|
structuralToolCalls,
|
|
5224
5311
|
terminalToolCalls,
|
|
5225
|
-
'The
|
|
5312
|
+
'The built-in HTTP stream'
|
|
5226
5313
|
);
|
|
5227
5314
|
await dataResultHandler(completion,id);
|
|
5228
5315
|
for(const call of terminalToolCalls){
|
|
@@ -5272,9 +5359,13 @@ class AI {
|
|
|
5272
5359
|
minP,
|
|
5273
5360
|
seed,
|
|
5274
5361
|
stop,
|
|
5275
|
-
templateOptions
|
|
5362
|
+
templateOptions,
|
|
5363
|
+
reasoningEffort
|
|
5276
5364
|
}={}){
|
|
5277
5365
|
validateAIStructuralRequest(messages,tools,parallelToolCalls);
|
|
5366
|
+
const normalizedReasoningEffort=normalizeAIReasoningEffort(
|
|
5367
|
+
reasoningEffort===undefined?this.reasoningEffort:reasoningEffort
|
|
5368
|
+
);
|
|
5278
5369
|
if(localOnly!==true&&localOnly!==false){
|
|
5279
5370
|
throw new TypeError('AI localOnly must be a boolean.');
|
|
5280
5371
|
}
|
|
@@ -5306,7 +5397,10 @@ class AI {
|
|
|
5306
5397
|
...(minP!==undefined?{minP}:{}),
|
|
5307
5398
|
...(seed!==undefined?{seed}:{}),
|
|
5308
5399
|
...(stop!==undefined?{stop}:{}),
|
|
5309
|
-
...(templateOptions!==undefined?{templateOptions}:{})
|
|
5400
|
+
...(templateOptions!==undefined?{templateOptions}:{}),
|
|
5401
|
+
...(normalizedReasoningEffort
|
|
5402
|
+
?{reasoningEffort:normalizedReasoningEffort}
|
|
5403
|
+
:{})
|
|
5310
5404
|
};
|
|
5311
5405
|
await this.#reportRequest(onRequest,request,id);
|
|
5312
5406
|
if(signal?.aborted){
|
|
@@ -5332,7 +5426,7 @@ class AI {
|
|
|
5332
5426
|
return response;
|
|
5333
5427
|
}
|
|
5334
5428
|
|
|
5335
|
-
return this
|
|
5429
|
+
return this.#fetchBuiltIn(
|
|
5336
5430
|
messages,
|
|
5337
5431
|
onResponse,
|
|
5338
5432
|
structuredOutput,
|
|
@@ -5341,7 +5435,8 @@ class AI {
|
|
|
5341
5435
|
parallelToolCalls,
|
|
5342
5436
|
id,
|
|
5343
5437
|
onRequest,
|
|
5344
|
-
signal
|
|
5438
|
+
signal,
|
|
5439
|
+
normalizedReasoningEffort
|
|
5345
5440
|
);
|
|
5346
5441
|
}
|
|
5347
5442
|
|
|
@@ -5354,7 +5449,7 @@ class AI {
|
|
|
5354
5449
|
parallel_tool_calls,
|
|
5355
5450
|
id=Date.now(),
|
|
5356
5451
|
requestHandler=function ignoreFetchRequest(){},
|
|
5357
|
-
signal=null
|
|
5452
|
+
signal=null
|
|
5358
5453
|
){
|
|
5359
5454
|
if(this.#shouldUseProviderRuntime('llm',this.llmService,false)){
|
|
5360
5455
|
return this.fetchRequest({
|
|
@@ -5371,7 +5466,7 @@ class AI {
|
|
|
5371
5466
|
});
|
|
5372
5467
|
}
|
|
5373
5468
|
|
|
5374
|
-
return this.#
|
|
5469
|
+
return this.#fetchBuiltIn(
|
|
5375
5470
|
messages,
|
|
5376
5471
|
responseHandler,
|
|
5377
5472
|
structuredOutput,
|
|
@@ -5384,7 +5479,7 @@ class AI {
|
|
|
5384
5479
|
);
|
|
5385
5480
|
}
|
|
5386
5481
|
|
|
5387
|
-
async #
|
|
5482
|
+
async #fetchBuiltIn(
|
|
5388
5483
|
messages=[],
|
|
5389
5484
|
responseHandler=function ignoreFetchResponse(){},
|
|
5390
5485
|
structuredOutput=false,
|
|
@@ -5394,6 +5489,7 @@ class AI {
|
|
|
5394
5489
|
id=Date.now(),
|
|
5395
5490
|
requestHandler=function ignoreFetchRequest(){},
|
|
5396
5491
|
signal=null,
|
|
5492
|
+
reasoningEffort
|
|
5397
5493
|
){
|
|
5398
5494
|
validateAIStructuralRequest(messages,tools,parallel_tool_calls);
|
|
5399
5495
|
this.#assertServiceConfigured(this.llmService);
|
|
@@ -5408,6 +5504,9 @@ class AI {
|
|
|
5408
5504
|
}
|
|
5409
5505
|
const structuredOutputFormat=this.#structuredOutputFormat(structuredOutput);
|
|
5410
5506
|
|
|
5507
|
+
const normalizedReasoningEffort=normalizeAIReasoningEffort(
|
|
5508
|
+
reasoningEffort===undefined?this.reasoningEffort:reasoningEffort
|
|
5509
|
+
);
|
|
5411
5510
|
const request={
|
|
5412
5511
|
model:this.model,
|
|
5413
5512
|
messages:messages,
|
|
@@ -5428,14 +5527,17 @@ class AI {
|
|
|
5428
5527
|
}
|
|
5429
5528
|
}
|
|
5430
5529
|
|
|
5431
|
-
if(
|
|
5432
|
-
|
|
5530
|
+
if(
|
|
5531
|
+
normalizedReasoningEffort
|
|
5532
|
+
&&(this.llmService==='TWIN'||this.llmService==='OLLAMA')
|
|
5533
|
+
){
|
|
5534
|
+
request.reasoning_effort=normalizedReasoningEffort;
|
|
5433
5535
|
}
|
|
5434
5536
|
|
|
5435
5537
|
const nativeOllama=this.#nativeOllama();
|
|
5436
5538
|
|
|
5437
5539
|
if(this.llmService==='OLLAMA'&&!nativeOllama){
|
|
5438
|
-
throw
|
|
5540
|
+
throw aiProviderError(
|
|
5439
5541
|
'Local AI requires the capability-gated Arcane API.',
|
|
5440
5542
|
'AI_NATIVE_LOCAL_REQUIRED'
|
|
5441
5543
|
);
|
|
@@ -5448,7 +5550,9 @@ class AI {
|
|
|
5448
5550
|
model:this.model,
|
|
5449
5551
|
messages:ollamaMessages,
|
|
5450
5552
|
stream:false,
|
|
5451
|
-
...(
|
|
5553
|
+
...(normalizedReasoningEffort
|
|
5554
|
+
?{think:normalizedReasoningEffort}
|
|
5555
|
+
:{}),
|
|
5452
5556
|
...(structuredOutputFormat?{format:structuredOutputFormat}:{}),
|
|
5453
5557
|
...(ollamaTools.length?{tools:ollamaTools}:{})
|
|
5454
5558
|
};
|
|
@@ -5756,18 +5860,21 @@ class AI {
|
|
|
5756
5860
|
#queueSpeechJob(text,generation){
|
|
5757
5861
|
const job={
|
|
5758
5862
|
abortController:null,
|
|
5863
|
+
audioBuffer:null,
|
|
5864
|
+
audioContext:null,
|
|
5759
5865
|
generation,
|
|
5866
|
+
scheduledEnd:null,
|
|
5867
|
+
scheduledStart:null,
|
|
5760
5868
|
sourceNode:null,
|
|
5761
5869
|
state:'queued',
|
|
5762
5870
|
text
|
|
5763
5871
|
};
|
|
5764
5872
|
const runtime=this;
|
|
5765
|
-
const previous=this.speechSynthesisTail;
|
|
5766
5873
|
|
|
5767
5874
|
this.speechJobs.push(job);
|
|
5768
5875
|
|
|
5769
|
-
|
|
5770
|
-
function
|
|
5876
|
+
return Promise.resolve().then(
|
|
5877
|
+
function synthesizeAvailableSpeech(){
|
|
5771
5878
|
return runtime.#prepareSpeechJob(job);
|
|
5772
5879
|
}
|
|
5773
5880
|
).catch(
|
|
@@ -5779,14 +5886,6 @@ class AI {
|
|
|
5779
5886
|
);
|
|
5780
5887
|
}
|
|
5781
5888
|
);
|
|
5782
|
-
|
|
5783
|
-
this.speechSynthesisTail=synthesis.then(
|
|
5784
|
-
function releaseSpeechSynthesisSlot(){
|
|
5785
|
-
return undefined;
|
|
5786
|
-
}
|
|
5787
|
-
);
|
|
5788
|
-
|
|
5789
|
-
return synthesis;
|
|
5790
5889
|
}
|
|
5791
5890
|
|
|
5792
5891
|
async #prepareSpeechJob(job){
|
|
@@ -5871,7 +5970,7 @@ class AI {
|
|
|
5871
5970
|
||!formats.every(format=>typeof format==='string'&&format.trim()===format&&format)
|
|
5872
5971
|
||typeof defaultFormat!=='string'
|
|
5873
5972
|
||!formats.includes(defaultFormat)){
|
|
5874
|
-
throw
|
|
5973
|
+
throw aiProviderError(
|
|
5875
5974
|
'The selected TTS provider returned an invalid speech format catalog.',
|
|
5876
5975
|
'ARCANE_AI_PROVIDER_RUNTIME_INVALID'
|
|
5877
5976
|
);
|
|
@@ -5879,10 +5978,10 @@ class AI {
|
|
|
5879
5978
|
if(formats.includes(this.audioFormat)){
|
|
5880
5979
|
return this.audioFormat;
|
|
5881
5980
|
}
|
|
5882
|
-
if(this.audioFormat===
|
|
5981
|
+
if(this.audioFormat===DEFAULT_TTS_RESPONSE_FORMAT){
|
|
5883
5982
|
return defaultFormat;
|
|
5884
5983
|
}
|
|
5885
|
-
throw
|
|
5984
|
+
throw aiProviderError(
|
|
5886
5985
|
`The selected TTS provider does not support ${this.audioFormat}.`,
|
|
5887
5986
|
'ARCANE_AI_UNSUPPORTED_RESPONSE_FORMAT'
|
|
5888
5987
|
);
|
|
@@ -6066,7 +6165,7 @@ class AI {
|
|
|
6066
6165
|
?requestedVoice
|
|
6067
6166
|
:selection
|
|
6068
6167
|
?this.#providerSpeechVoice()
|
|
6069
|
-
:this.#
|
|
6168
|
+
:this.#builtInSpeechDefaultVoice('tts',this.ttsService);
|
|
6070
6169
|
if(!voice){
|
|
6071
6170
|
const error=new TypeError(
|
|
6072
6171
|
'AI.fetchTTS requires a caller- or model-catalog-admitted voice.'
|
|
@@ -6118,23 +6217,14 @@ class AI {
|
|
|
6118
6217
|
return audio;
|
|
6119
6218
|
}
|
|
6120
6219
|
|
|
6121
|
-
return this.#
|
|
6220
|
+
return this.#requestBuiltInSpeechSynthesis(
|
|
6122
6221
|
{model,voice,input,responseFormat,speed},
|
|
6123
6222
|
signal
|
|
6124
6223
|
);
|
|
6125
6224
|
}
|
|
6126
6225
|
|
|
6127
|
-
async fetchSTT(
|
|
6128
|
-
audioFile,
|
|
6129
|
-
responseHandler=(text='')=>{},
|
|
6130
|
-
signal=null
|
|
6131
|
-
){
|
|
6226
|
+
async fetchSTT(audioFile,signal=null){
|
|
6132
6227
|
this.#assertServiceConfigured(this.sttService,'stt');
|
|
6133
|
-
if(typeof responseHandler!=='function'){
|
|
6134
|
-
const error=new TypeError('AI.fetchSTT responseHandler must be a function.');
|
|
6135
|
-
error.code='ARCANE_AI_STT_RESPONSE_HANDLER_INVALID';
|
|
6136
|
-
throw error;
|
|
6137
|
-
}
|
|
6138
6228
|
if(signal&&(
|
|
6139
6229
|
typeof signal.aborted!=='boolean'
|
|
6140
6230
|
||typeof signal.addEventListener!=='function'
|
|
@@ -6176,12 +6266,10 @@ class AI {
|
|
|
6176
6266
|
throw error;
|
|
6177
6267
|
}
|
|
6178
6268
|
if(signal?.aborted)throw normalizeAIRequestAbort(signal.reason);
|
|
6179
|
-
await responseHandler(text);
|
|
6180
|
-
if(signal?.aborted)throw normalizeAIRequestAbort(signal.reason);
|
|
6181
6269
|
return text;
|
|
6182
6270
|
}
|
|
6183
6271
|
|
|
6184
|
-
const text=await this.#
|
|
6272
|
+
const text=await this.#requestBuiltInSpeechTranscription(
|
|
6185
6273
|
{
|
|
6186
6274
|
audio:audioFile,
|
|
6187
6275
|
mimeType:String(audioFile?.type||'audio/webm'),
|
|
@@ -6190,8 +6278,6 @@ class AI {
|
|
|
6190
6278
|
signal
|
|
6191
6279
|
);
|
|
6192
6280
|
if(signal?.aborted)throw normalizeAIRequestAbort(signal.reason);
|
|
6193
|
-
await responseHandler(text);
|
|
6194
|
-
if(signal?.aborted)throw normalizeAIRequestAbort(signal.reason);
|
|
6195
6281
|
return text;
|
|
6196
6282
|
}
|
|
6197
6283
|
|
|
@@ -6199,6 +6285,9 @@ class AI {
|
|
|
6199
6285
|
this.speechGeneration+=1;
|
|
6200
6286
|
this.speechResumeAttempt+=1;
|
|
6201
6287
|
this.speechResumePending=false;
|
|
6288
|
+
this.speechScheduleGeneration=this.speechGeneration;
|
|
6289
|
+
this.speechScheduleContext=null;
|
|
6290
|
+
this.speechScheduleTime=0;
|
|
6202
6291
|
this.audioMessageChunks='';
|
|
6203
6292
|
this.#clearSpeechUnlock();
|
|
6204
6293
|
|
|
@@ -6254,7 +6343,7 @@ class AI {
|
|
|
6254
6343
|
}
|
|
6255
6344
|
|
|
6256
6345
|
if(typeof context.resume!=='function'){
|
|
6257
|
-
this.#waitForSpeechGesture();
|
|
6346
|
+
this.#waitForSpeechGesture(null,context);
|
|
6258
6347
|
return false;
|
|
6259
6348
|
}
|
|
6260
6349
|
|
|
@@ -6281,7 +6370,7 @@ class AI {
|
|
|
6281
6370
|
if(attempt===this.speechResumeAttempt){
|
|
6282
6371
|
this.speechResumePending=false;
|
|
6283
6372
|
}
|
|
6284
|
-
this.#waitForSpeechGesture(error);
|
|
6373
|
+
this.#waitForSpeechGesture(error,context);
|
|
6285
6374
|
if(error?.name!=='NotAllowedError'){
|
|
6286
6375
|
this.#publishTTSFailure(error,{
|
|
6287
6376
|
boundary:'playback-resume',
|
|
@@ -6291,7 +6380,7 @@ class AI {
|
|
|
6291
6380
|
return false;
|
|
6292
6381
|
}
|
|
6293
6382
|
|
|
6294
|
-
this.#waitForSpeechGesture();
|
|
6383
|
+
this.#waitForSpeechGesture(null,context);
|
|
6295
6384
|
return false;
|
|
6296
6385
|
}
|
|
6297
6386
|
|
|
@@ -6304,7 +6393,11 @@ class AI {
|
|
|
6304
6393
|
){
|
|
6305
6394
|
const job=speechJob||{
|
|
6306
6395
|
abortController:null,
|
|
6396
|
+
audioBuffer:null,
|
|
6397
|
+
audioContext:null,
|
|
6307
6398
|
generation:this.speechGeneration,
|
|
6399
|
+
scheduledEnd:null,
|
|
6400
|
+
scheduledStart:null,
|
|
6308
6401
|
sourceNode:null,
|
|
6309
6402
|
state:'decoding',
|
|
6310
6403
|
text:''
|
|
@@ -6320,7 +6413,9 @@ class AI {
|
|
|
6320
6413
|
|
|
6321
6414
|
try{
|
|
6322
6415
|
job.state='decoding';
|
|
6323
|
-
const playbackContext=
|
|
6416
|
+
const playbackContext=sourceNode?.context
|
|
6417
|
+
||audioContext
|
|
6418
|
+
||this.#getSpeechAudioContext();
|
|
6324
6419
|
const audioBlob=new Blob(audioChunks,{type:audioType});
|
|
6325
6420
|
const arrayBuffer=await audioBlob.arrayBuffer();
|
|
6326
6421
|
const audioBuffer=await playbackContext.decodeAudioData(arrayBuffer);
|
|
@@ -6338,6 +6433,8 @@ class AI {
|
|
|
6338
6433
|
preparedSource.onended=function finishQueuedSpeechSource(){
|
|
6339
6434
|
runtime.nextSentance(job);
|
|
6340
6435
|
};
|
|
6436
|
+
job.audioBuffer=audioBuffer;
|
|
6437
|
+
job.audioContext=preparedSource.context||playbackContext;
|
|
6341
6438
|
job.sourceNode=preparedSource;
|
|
6342
6439
|
job.state='ready';
|
|
6343
6440
|
this.sourceNodes.push(preparedSource);
|
|
@@ -6363,7 +6460,7 @@ class AI {
|
|
|
6363
6460
|
}
|
|
6364
6461
|
|
|
6365
6462
|
async #pumpSpeechPlayback(){
|
|
6366
|
-
if(this.speechPlaybackStarting||this.
|
|
6463
|
+
if(this.speechPlaybackStarting||this.muted){
|
|
6367
6464
|
return false;
|
|
6368
6465
|
}
|
|
6369
6466
|
|
|
@@ -6371,12 +6468,21 @@ class AI {
|
|
|
6371
6468
|
let activeJob=null;
|
|
6372
6469
|
|
|
6373
6470
|
try{
|
|
6374
|
-
|
|
6375
|
-
|
|
6471
|
+
if(this.speechScheduleGeneration!==this.speechGeneration){
|
|
6472
|
+
this.speechScheduleGeneration=this.speechGeneration;
|
|
6473
|
+
this.speechScheduleContext=null;
|
|
6474
|
+
this.speechScheduleTime=0;
|
|
6475
|
+
}
|
|
6476
|
+
|
|
6477
|
+
let index=0;
|
|
6478
|
+
let scheduled=false;
|
|
6479
|
+
|
|
6480
|
+
while(index<this.speechJobs.length&&!this.muted){
|
|
6481
|
+
const job=this.speechJobs[index];
|
|
6376
6482
|
activeJob=job||null;
|
|
6377
6483
|
|
|
6378
6484
|
if(!job){
|
|
6379
|
-
|
|
6485
|
+
break;
|
|
6380
6486
|
}
|
|
6381
6487
|
|
|
6382
6488
|
if(job.generation!==this.speechGeneration||['cancelled','failed'].includes(job.state)){
|
|
@@ -6384,25 +6490,32 @@ class AI {
|
|
|
6384
6490
|
continue;
|
|
6385
6491
|
}
|
|
6386
6492
|
|
|
6387
|
-
if(job.state
|
|
6388
|
-
|
|
6493
|
+
if(!['ready','scheduled'].includes(job.state)||!job.sourceNode?.buffer){
|
|
6494
|
+
break;
|
|
6389
6495
|
}
|
|
6390
6496
|
|
|
6391
|
-
const audioContext=
|
|
6497
|
+
const audioContext=job.sourceNode.context||job.audioContext;
|
|
6392
6498
|
|
|
6393
6499
|
if(audioContext.state!=='running'){
|
|
6394
|
-
this.#waitForSpeechGesture();
|
|
6500
|
+
this.#waitForSpeechGesture(null,audioContext);
|
|
6395
6501
|
|
|
6396
6502
|
if(!this.speechResumePending){
|
|
6397
6503
|
this.resumeAudio(audioContext,false);
|
|
6398
6504
|
}
|
|
6399
6505
|
|
|
6400
|
-
return
|
|
6506
|
+
return scheduled;
|
|
6507
|
+
}
|
|
6508
|
+
|
|
6509
|
+
if(job.state==='scheduled'){
|
|
6510
|
+
if(job.scheduledEnd===null){
|
|
6511
|
+
break;
|
|
6512
|
+
}
|
|
6513
|
+
index+=1;
|
|
6514
|
+
continue;
|
|
6401
6515
|
}
|
|
6402
6516
|
|
|
6403
6517
|
if(
|
|
6404
6518
|
this.muted
|
|
6405
|
-
||job!==this.speechJobs[0]
|
|
6406
6519
|
||job.generation!==this.speechGeneration
|
|
6407
6520
|
||job.state!=='ready'
|
|
6408
6521
|
){
|
|
@@ -6410,18 +6523,47 @@ class AI {
|
|
|
6410
6523
|
}
|
|
6411
6524
|
|
|
6412
6525
|
try{
|
|
6413
|
-
job.
|
|
6526
|
+
const duration=Number(job.audioBuffer?.duration);
|
|
6527
|
+
const hasKnownDuration=Number.isFinite(duration)&&duration>0;
|
|
6528
|
+
const currentTime=Number(audioContext.currentTime)||0;
|
|
6529
|
+
const previousContext=this.speechScheduleContext;
|
|
6530
|
+
const remainingDelay=previousContext
|
|
6531
|
+
?Math.max(
|
|
6532
|
+
0,
|
|
6533
|
+
this.speechScheduleTime
|
|
6534
|
+
-(Number(previousContext.currentTime)||0)
|
|
6535
|
+
)
|
|
6536
|
+
:0;
|
|
6537
|
+
const scheduledStart=Math.max(
|
|
6538
|
+
currentTime,
|
|
6539
|
+
previousContext===audioContext
|
|
6540
|
+
?this.speechScheduleTime
|
|
6541
|
+
:currentTime+remainingDelay
|
|
6542
|
+
);
|
|
6543
|
+
job.state='scheduled';
|
|
6544
|
+
job.scheduledStart=scheduledStart;
|
|
6545
|
+
job.scheduledEnd=hasKnownDuration
|
|
6546
|
+
?scheduledStart+duration
|
|
6547
|
+
:null;
|
|
6414
6548
|
job.sourceNode.__arcaneStarted=true;
|
|
6415
|
-
this.currentSpeechJob
|
|
6549
|
+
if(!this.currentSpeechJob){
|
|
6550
|
+
this.currentSpeechJob=job;
|
|
6551
|
+
}
|
|
6416
6552
|
this.isSpeaking=true;
|
|
6417
|
-
|
|
6418
|
-
|
|
6553
|
+
job.sourceNode.start(scheduledStart);
|
|
6554
|
+
scheduled=true;
|
|
6555
|
+
this.speechScheduleContext=audioContext;
|
|
6556
|
+
if(job.scheduledEnd===null){
|
|
6557
|
+
this.speechScheduleTime=0;
|
|
6558
|
+
break;
|
|
6559
|
+
}
|
|
6560
|
+
this.speechScheduleTime=job.scheduledEnd;
|
|
6561
|
+
index+=1;
|
|
6419
6562
|
}catch(error){
|
|
6420
|
-
this.currentSpeechJob=null;
|
|
6421
|
-
this.isSpeaking=false;
|
|
6422
6563
|
this.#failSpeechJob(job,error,'playback-start');
|
|
6423
6564
|
}
|
|
6424
6565
|
}
|
|
6566
|
+
return scheduled;
|
|
6425
6567
|
}catch(error){
|
|
6426
6568
|
if(activeJob){
|
|
6427
6569
|
this.#failSpeechJob(activeJob,error,'playback-start');
|
|
@@ -6433,11 +6575,20 @@ class AI {
|
|
|
6433
6575
|
this.speechPlaybackStarting=false;
|
|
6434
6576
|
|
|
6435
6577
|
if(
|
|
6436
|
-
!this.
|
|
6437
|
-
&&!this.muted
|
|
6578
|
+
!this.muted
|
|
6438
6579
|
&&!this.speechAwaitingGesture
|
|
6439
6580
|
&&!this.speechResumePending
|
|
6440
|
-
|
|
6581
|
+
&&!this.speechJobs.some(
|
|
6582
|
+
function hasScheduledSpeechWithoutDuration(job){
|
|
6583
|
+
return job.state==='scheduled'
|
|
6584
|
+
&&job.scheduledEnd===null;
|
|
6585
|
+
}
|
|
6586
|
+
)
|
|
6587
|
+
&&this.speechJobs.find(
|
|
6588
|
+
function findFirstUnscheduledSpeechJob(job){
|
|
6589
|
+
return job.state!=='scheduled';
|
|
6590
|
+
}
|
|
6591
|
+
)?.state==='ready'
|
|
6441
6592
|
){
|
|
6442
6593
|
this.#requestSpeechPlayback();
|
|
6443
6594
|
}
|
|
@@ -6459,9 +6610,17 @@ class AI {
|
|
|
6459
6610
|
this.#removeSpeechJob(job);
|
|
6460
6611
|
|
|
6461
6612
|
if(this.currentSpeechJob===job){
|
|
6462
|
-
this.currentSpeechJob=
|
|
6463
|
-
|
|
6613
|
+
this.currentSpeechJob=this.speechJobs.find(
|
|
6614
|
+
function findNextScheduledSpeechJob(candidate){
|
|
6615
|
+
return candidate.state==='scheduled';
|
|
6616
|
+
}
|
|
6617
|
+
)||null;
|
|
6464
6618
|
}
|
|
6619
|
+
this.isSpeaking=this.speechJobs.some(
|
|
6620
|
+
function hasScheduledSpeechJob(candidate){
|
|
6621
|
+
return candidate.state==='scheduled';
|
|
6622
|
+
}
|
|
6623
|
+
);
|
|
6465
6624
|
|
|
6466
6625
|
this.#requestSpeechPlayback();
|
|
6467
6626
|
return true;
|
|
@@ -6551,9 +6710,17 @@ class AI {
|
|
|
6551
6710
|
this.#removeSpeechJob(job);
|
|
6552
6711
|
|
|
6553
6712
|
if(this.currentSpeechJob===job){
|
|
6554
|
-
this.currentSpeechJob=
|
|
6555
|
-
|
|
6713
|
+
this.currentSpeechJob=this.speechJobs.find(
|
|
6714
|
+
function findRemainingScheduledSpeechJob(candidate){
|
|
6715
|
+
return candidate.state==='scheduled';
|
|
6716
|
+
}
|
|
6717
|
+
)||null;
|
|
6556
6718
|
}
|
|
6719
|
+
this.isSpeaking=this.speechJobs.some(
|
|
6720
|
+
function hasRemainingScheduledSpeechJob(candidate){
|
|
6721
|
+
return candidate.state==='scheduled';
|
|
6722
|
+
}
|
|
6723
|
+
);
|
|
6557
6724
|
|
|
6558
6725
|
this.#requestSpeechPlayback();
|
|
6559
6726
|
return false;
|
|
@@ -6575,7 +6742,7 @@ class AI {
|
|
|
6575
6742
|
job.sourceNode?.disconnect?.();
|
|
6576
6743
|
}
|
|
6577
6744
|
|
|
6578
|
-
#waitForSpeechGesture(error=null){
|
|
6745
|
+
#waitForSpeechGesture(error=null,audioContext=null){
|
|
6579
6746
|
if(this.speechUnlockHandler){
|
|
6580
6747
|
return false;
|
|
6581
6748
|
}
|
|
@@ -6586,7 +6753,7 @@ class AI {
|
|
|
6586
6753
|
this.speechAwaitingGesture=true;
|
|
6587
6754
|
this.speechUnlockHandler=function unlockSpeechFromUserGesture(){
|
|
6588
6755
|
runtime.#clearSpeechUnlock();
|
|
6589
|
-
runtime.resumeAudio();
|
|
6756
|
+
return runtime.resumeAudio(audioContext);
|
|
6590
6757
|
};
|
|
6591
6758
|
|
|
6592
6759
|
target.addEventListener?.(
|