arcane-os 0.3.3 → 0.3.5
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 +22 -0
- package/README.md +7 -7
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +315 -119
- package/browser-runtime/ai/model-controller.mjs +439 -95
- package/package.json +1 -1
- package/runtime/arcane/components/assistant-panel.html +2 -1
- package/runtime/arcane/components/chat.html +466 -206
- package/runtime/arcane/components/speech.html +107 -30
- package/runtime/arcane/components/voice-transcription.html +27 -3
- package/runtime/arcane/entities/Chat.js +165 -97
- package/runtime/arcane/entities/IntentEnvelope.js +52 -118
- package/runtime/arcane/entities/TWiNPolicyDecision.js +33 -130
- package/runtime/arcane/entities/User.js +38 -44
- package/runtime/arcane/modules/AI.js +569 -302
- package/runtime/arcane/modules/AIProviderRuntime.js +765 -135
- package/runtime/arcane/modules/AIRuntimeState.js +22 -24
- package/runtime/arcane/modules/ApiModelDatabase.js +54 -48
- package/runtime/arcane/modules/CaseEvidenceIndexer.js +6 -10
- package/runtime/arcane/modules/CommunicationHub.js +90 -94
- package/runtime/arcane/modules/ComponentContracts.js +34 -9
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +77 -77
- package/runtime/arcane/modules/ConversationTimebox.js +76 -104
- package/runtime/arcane/modules/DBLS.js +14 -12
- package/runtime/arcane/modules/DBOPFS.js +20 -15
- package/runtime/arcane/modules/Errors.js +196 -436
- package/runtime/arcane/modules/HTMLImport.js +49 -32
- package/runtime/arcane/modules/Ollama.js +16 -14
- package/runtime/arcane/modules/PersistentAIChatSession.js +174 -93
- package/runtime/arcane/modules/RecordReviewStore.js +40 -35
- package/runtime/arcane/modules/TerminalClient.js +12 -14
- package/runtime/arcane/modules/ThemeBootstrap.js +7 -7
- package/runtime/arcane/modules/ThemeManager.js +5 -5
- package/runtime/arcane/modules/TimeGuard.js +5 -65
- package/runtime/arcane/modules/WaitForComponent.js +43 -40
- package/src/cli/main.mjs +12 -11
- package/src/installed-sdk-runtime.mjs +11 -1
- package/src/mail-server.mjs +12 -5
- package/src/mail.mjs +25 -19
- package/src/workspace.mjs +32 -9
|
@@ -214,9 +214,11 @@ function normalizeAIStructuralToolCall(call,label='Structural tool call'){
|
|
|
214
214
|
);
|
|
215
215
|
}
|
|
216
216
|
return {
|
|
217
|
+
...call,
|
|
217
218
|
id:call.id,
|
|
218
219
|
type:'function',
|
|
219
220
|
function:{
|
|
221
|
+
...call.function,
|
|
220
222
|
name:call.function.name,
|
|
221
223
|
arguments:call.function.arguments
|
|
222
224
|
}
|
|
@@ -227,7 +229,7 @@ function validateAIRequestMessages(messages=[]){
|
|
|
227
229
|
if(!Array.isArray(messages)){
|
|
228
230
|
throw new TypeError('AI messages must be an array.');
|
|
229
231
|
}
|
|
230
|
-
|
|
232
|
+
const pendingToolCallIds=new Set();
|
|
231
233
|
for(let messageIndex=0;messageIndex<messages.length;messageIndex+=1){
|
|
232
234
|
const message=messages[messageIndex];
|
|
233
235
|
const calls=message?.tool_calls;
|
|
@@ -239,17 +241,26 @@ function validateAIRequestMessages(messages=[]){
|
|
|
239
241
|
`AI messages[${messageIndex}].tool_calls is invalid.`
|
|
240
242
|
);
|
|
241
243
|
}
|
|
242
|
-
if(
|
|
244
|
+
if(pendingToolCallIds.size&&calls.length){
|
|
243
245
|
throw aiStructuralError(
|
|
244
|
-
'
|
|
245
|
-
'
|
|
246
|
+
'AI_CHAT_TOOL_RESULT_REQUIRED',
|
|
247
|
+
'Every pending structural tool result must be supplied before another assistant tool-call sequence.'
|
|
246
248
|
);
|
|
247
249
|
}
|
|
248
250
|
if(calls.length){
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
for(let callIndex=0;callIndex<calls.length;callIndex+=1){
|
|
252
|
+
const normalized=normalizeAIStructuralToolCall(
|
|
253
|
+
calls[callIndex],
|
|
254
|
+
`AI messages[${messageIndex}].tool_calls[${callIndex}]`
|
|
255
|
+
);
|
|
256
|
+
if(pendingToolCallIds.has(normalized.id)){
|
|
257
|
+
throw aiStructuralError(
|
|
258
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
259
|
+
`AI messages[${messageIndex}].tool_calls contains a duplicate ID.`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
pendingToolCallIds.add(normalized.id);
|
|
263
|
+
}
|
|
253
264
|
openedToolCall=true;
|
|
254
265
|
}
|
|
255
266
|
}
|
|
@@ -261,24 +272,24 @@ function validateAIRequestMessages(messages=[]){
|
|
|
261
272
|
);
|
|
262
273
|
}
|
|
263
274
|
if(
|
|
264
|
-
|
|
275
|
+
!pendingToolCallIds.size
|
|
265
276
|
||typeof message.tool_call_id!=='string'
|
|
266
|
-
|
|
277
|
+
||!pendingToolCallIds.has(message.tool_call_id)
|
|
267
278
|
){
|
|
268
279
|
throw aiStructuralError(
|
|
269
280
|
'AI_CHAT_INVALID_TOOL_MESSAGE',
|
|
270
281
|
`AI messages[${messageIndex}] does not settle the pending structural tool call.`
|
|
271
282
|
);
|
|
272
283
|
}
|
|
273
|
-
|
|
274
|
-
}else if(
|
|
284
|
+
pendingToolCallIds.delete(message.tool_call_id);
|
|
285
|
+
}else if(pendingToolCallIds.size&&!openedToolCall){
|
|
275
286
|
throw aiStructuralError(
|
|
276
287
|
'AI_CHAT_TOOL_RESULT_REQUIRED',
|
|
277
288
|
`AI messages[${messageIndex}] precedes the pending structural tool result.`
|
|
278
289
|
);
|
|
279
290
|
}
|
|
280
291
|
}
|
|
281
|
-
if(
|
|
292
|
+
if(pendingToolCallIds.size){
|
|
282
293
|
throw aiStructuralError(
|
|
283
294
|
'AI_CHAT_TOOL_RESULT_REQUIRED',
|
|
284
295
|
'The pending structural tool call must be settled before requesting another response.'
|
|
@@ -289,11 +300,8 @@ function validateAIRequestMessages(messages=[]){
|
|
|
289
300
|
function validateAIStructuralRequest(messages,tools,parallelToolCalls){
|
|
290
301
|
validateAIRequestMessages(messages);
|
|
291
302
|
requireAIToolMessageSchemas(tools);
|
|
292
|
-
if(parallelToolCalls
|
|
293
|
-
throw
|
|
294
|
-
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED',
|
|
295
|
-
'The Arcane chat session accepts one structural tool call at a time.'
|
|
296
|
-
);
|
|
303
|
+
if(parallelToolCalls!==undefined&&typeof parallelToolCalls!=='boolean'){
|
|
304
|
+
throw new TypeError('AI parallelToolCalls must be a boolean when provided.');
|
|
297
305
|
}
|
|
298
306
|
}
|
|
299
307
|
|
|
@@ -334,29 +342,49 @@ function normalizeAICompletionToolCalls(completion){
|
|
|
334
342
|
`AI response message ${messageIndex+1} contains invalid structural tool calls.`
|
|
335
343
|
);
|
|
336
344
|
}
|
|
337
|
-
|
|
338
|
-
throw aiStructuralError(
|
|
339
|
-
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED',
|
|
340
|
-
'The Arcane chat session accepts one structural tool call at a time.'
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
if(messageIndex>0&&toolCalls?.length){
|
|
344
|
-
throw aiStructuralError(
|
|
345
|
-
'AI_CHAT_INVALID_RESPONSE',
|
|
346
|
-
'The AI completion placed a structural tool call outside the selected choice.'
|
|
347
|
-
);
|
|
348
|
-
}
|
|
345
|
+
const messageIds=new Set();
|
|
349
346
|
for(let callIndex=0;callIndex<(toolCalls??[]).length;callIndex+=1){
|
|
350
|
-
|
|
347
|
+
const normalized=normalizeAIStructuralToolCall(
|
|
351
348
|
toolCalls[callIndex],
|
|
352
349
|
`AI response structural tool call ${messageIndex+1}.${callIndex+1}`
|
|
353
|
-
)
|
|
350
|
+
);
|
|
351
|
+
if(messageIds.has(normalized.id)){
|
|
352
|
+
throw aiStructuralError(
|
|
353
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
354
|
+
`AI response message ${messageIndex+1} contains a duplicate structural tool-call ID.`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
messageIds.add(normalized.id);
|
|
358
|
+
if(messageIndex===0)calls.push(normalized);
|
|
354
359
|
}
|
|
355
360
|
}
|
|
356
361
|
return calls;
|
|
357
362
|
}
|
|
358
363
|
|
|
364
|
+
function sameAIDataValue(left,right,seen=new Map()){
|
|
365
|
+
if(Object.is(left,right))return true;
|
|
366
|
+
if(!left||!right||typeof left!=='object'||typeof right!=='object')return false;
|
|
367
|
+
if(Array.isArray(left)!==Array.isArray(right))return false;
|
|
368
|
+
const matched=seen.get(left);
|
|
369
|
+
if(matched!==undefined)return matched===right;
|
|
370
|
+
seen.set(left,right);
|
|
371
|
+
if(Array.isArray(left)){
|
|
372
|
+
return left.length===right.length
|
|
373
|
+
&&left.every((value,index)=>sameAIDataValue(value,right[index],seen));
|
|
374
|
+
}
|
|
375
|
+
if(!isPlainAIRecord(left)||!isPlainAIRecord(right))return false;
|
|
376
|
+
const leftKeys=Reflect.ownKeys(left).filter(key=>Object.prototype.propertyIsEnumerable.call(left,key));
|
|
377
|
+
const rightKeys=Reflect.ownKeys(right).filter(key=>Object.prototype.propertyIsEnumerable.call(right,key));
|
|
378
|
+
return leftKeys.length===rightKeys.length
|
|
379
|
+
&&leftKeys.every(key=>Object.prototype.propertyIsEnumerable.call(right,key)
|
|
380
|
+
&&sameAIDataValue(left[key],right[key],seen));
|
|
381
|
+
}
|
|
382
|
+
|
|
359
383
|
function sameAIStructuralToolCall(left,right){
|
|
384
|
+
return sameAIDataValue(left,right);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function sameAICanonicalToolCall(left,right){
|
|
360
388
|
return left?.id===right?.id
|
|
361
389
|
&&left?.type===right?.type
|
|
362
390
|
&&left?.function?.name===right?.function?.name
|
|
@@ -402,6 +430,54 @@ function normalizeAIStreamToolCallObservation(completion,label){
|
|
|
402
430
|
}
|
|
403
431
|
}
|
|
404
432
|
|
|
433
|
+
function isAIStreamStructuralKey(key){
|
|
434
|
+
return key==='tool_calls'
|
|
435
|
+
||key==='toolCalls'
|
|
436
|
+
||key==='tool_call'
|
|
437
|
+
||key==='toolCall'
|
|
438
|
+
||key==='function_call'
|
|
439
|
+
||key==='functionCall';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const OMITTED_AI_STREAM_DATA=Symbol('omitted-ai-stream-data');
|
|
443
|
+
|
|
444
|
+
function projectAIStreamData(value,seen=new Map()){
|
|
445
|
+
if(value===null||value===undefined||typeof value!=='object'){
|
|
446
|
+
return value;
|
|
447
|
+
}
|
|
448
|
+
if(seen.has(value)) return seen.get(value);
|
|
449
|
+
if(Array.isArray(value)){
|
|
450
|
+
const result=[];
|
|
451
|
+
seen.set(value,result);
|
|
452
|
+
for(const item of value){
|
|
453
|
+
const projected=projectAIStreamData(item,seen);
|
|
454
|
+
if(projected!==OMITTED_AI_STREAM_DATA)result.push(projected);
|
|
455
|
+
}
|
|
456
|
+
return result.length||value.length===0?result:OMITTED_AI_STREAM_DATA;
|
|
457
|
+
}
|
|
458
|
+
const result={};
|
|
459
|
+
seen.set(value,result);
|
|
460
|
+
let sourceDataFields=0;
|
|
461
|
+
for(const [key,descriptor] of Object.entries(
|
|
462
|
+
Object.getOwnPropertyDescriptors(value)
|
|
463
|
+
)){
|
|
464
|
+
if(!Object.hasOwn(descriptor,'value')){
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
sourceDataFields+=1;
|
|
468
|
+
if(isAIStreamStructuralKey(key))continue;
|
|
469
|
+
const projected=projectAIStreamData(descriptor.value,seen);
|
|
470
|
+
if(projected!==OMITTED_AI_STREAM_DATA)result[key]=projected;
|
|
471
|
+
}
|
|
472
|
+
return Object.keys(result).length||sourceDataFields===0
|
|
473
|
+
?result
|
|
474
|
+
:OMITTED_AI_STREAM_DATA;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function projectAIStreamChunk(value){
|
|
478
|
+
return projectAIStreamData(value);
|
|
479
|
+
}
|
|
480
|
+
|
|
405
481
|
function createLegacyAIStreamBridge(execute,sourceSignal){
|
|
406
482
|
const controller=new AbortController();
|
|
407
483
|
const queue=[];
|
|
@@ -3930,13 +4006,21 @@ class AI {
|
|
|
3930
4006
|
|
|
3931
4007
|
#providerStreamEmissions(chunk,seeThinking){
|
|
3932
4008
|
const chunks=[];
|
|
4009
|
+
if(typeof chunk==='string'){
|
|
4010
|
+
if(chunk) chunks.push({text:chunk,thinking:false});
|
|
4011
|
+
return {chunks};
|
|
4012
|
+
}
|
|
3933
4013
|
const choices=Array.isArray(chunk?.choices)?chunk.choices:[];
|
|
3934
4014
|
for(const choice of choices){
|
|
3935
4015
|
const delta=choice?.delta||{};
|
|
3936
|
-
if(
|
|
4016
|
+
if(
|
|
4017
|
+
seeThinking
|
|
4018
|
+
&&typeof delta.reasoning_content==='string'
|
|
4019
|
+
&&delta.reasoning_content
|
|
4020
|
+
){
|
|
3937
4021
|
chunks.push({text:delta.reasoning_content,thinking:true});
|
|
3938
4022
|
}
|
|
3939
|
-
if(typeof delta.content==='string'){
|
|
4023
|
+
if(typeof delta.content==='string'&&delta.content){
|
|
3940
4024
|
chunks.push({text:delta.content,thinking:false});
|
|
3941
4025
|
}
|
|
3942
4026
|
}
|
|
@@ -3960,34 +4044,26 @@ class AI {
|
|
|
3960
4044
|
if(typeof completion==='string'){
|
|
3961
4045
|
return completion;
|
|
3962
4046
|
}
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
for(const choice of Array.isArray(completion?.choices)?completion.choices:[]){
|
|
3966
|
-
for(const call of Array.isArray(choice?.message?.tool_calls)
|
|
3967
|
-
?choice.message.tool_calls
|
|
3968
|
-
:[]){
|
|
3969
|
-
const name=call?.function?.name;
|
|
3970
|
-
if(typeof name==='string'&&name){
|
|
3971
|
-
toolRecord[name]=call.function.arguments;
|
|
3972
|
-
toolCount+=1;
|
|
3973
|
-
}
|
|
3974
|
-
}
|
|
3975
|
-
}
|
|
3976
|
-
if(toolCount){
|
|
3977
|
-
return toolRecord;
|
|
4047
|
+
if(Array.isArray(completion?.choices)&&completion.choices.length>1){
|
|
4048
|
+
return completion;
|
|
3978
4049
|
}
|
|
4050
|
+
const structuralToolCalls=normalizeAICompletionToolCalls(completion);
|
|
4051
|
+
if(structuralToolCalls.length)return structuralToolCalls;
|
|
3979
4052
|
const content=completion?.choices?.[0]?.message?.content;
|
|
3980
4053
|
return typeof content==='string'?content:completion;
|
|
3981
4054
|
}
|
|
3982
4055
|
|
|
3983
4056
|
#requestLegacyLLMChat(payload={},signal=null){
|
|
4057
|
+
const parallelToolCalls=payload.parallelToolCalls!==undefined
|
|
4058
|
+
?payload.parallelToolCalls
|
|
4059
|
+
:payload.parallel_tool_calls;
|
|
3984
4060
|
return this.#fetchLegacy(
|
|
3985
4061
|
payload.messages??[],
|
|
3986
4062
|
function ignoreLegacyLLMProviderResponse(){},
|
|
3987
4063
|
payload.structuredOutput??false,
|
|
3988
4064
|
payload.tools??[],
|
|
3989
4065
|
payload.toolChoice??'auto',
|
|
3990
|
-
|
|
4066
|
+
parallelToolCalls,
|
|
3991
4067
|
payload.id??Date.now(),
|
|
3992
4068
|
function ignoreLegacyLLMProviderRequest(){},
|
|
3993
4069
|
signal
|
|
@@ -3995,32 +4071,29 @@ class AI {
|
|
|
3995
4071
|
}
|
|
3996
4072
|
|
|
3997
4073
|
#requestLegacyLLMStream(payload={},bridge){
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
bridge.emit(
|
|
4003
|
-
thinking
|
|
4004
|
-
?{thinking:text}
|
|
4005
|
-
:{content:text}
|
|
4006
|
-
);
|
|
4074
|
+
const parallelToolCalls=payload.parallelToolCalls!==undefined
|
|
4075
|
+
?payload.parallelToolCalls
|
|
4076
|
+
:payload.parallel_tool_calls;
|
|
4077
|
+
function emitLegacyLLMStreamData(chunk){
|
|
4078
|
+
bridge.emit(chunk);
|
|
4007
4079
|
}
|
|
4008
4080
|
|
|
4009
4081
|
return this.#streamLegacyMessage(
|
|
4010
4082
|
payload.messages??[],
|
|
4011
|
-
|
|
4083
|
+
function ignoreLegacyLLMScalarStream(){},
|
|
4012
4084
|
function ignoreLegacyLLMProviderCompletion(){},
|
|
4013
4085
|
payload.tools??[],
|
|
4014
4086
|
payload.toolChoice??'auto',
|
|
4015
4087
|
function retainLegacyLLMStreamToolUntilCompletion(){},
|
|
4016
|
-
|
|
4088
|
+
parallelToolCalls,
|
|
4017
4089
|
payload.id??Date.now(),
|
|
4018
4090
|
payload.seeThinking??false,
|
|
4019
4091
|
bridge.signal,
|
|
4020
4092
|
function ignoreLegacyLLMProviderRequest(){},
|
|
4021
4093
|
payload.structuredOutput??false,
|
|
4022
4094
|
false,
|
|
4023
|
-
true
|
|
4095
|
+
true,
|
|
4096
|
+
emitLegacyLLMStreamData
|
|
4024
4097
|
);
|
|
4025
4098
|
}
|
|
4026
4099
|
|
|
@@ -4087,11 +4160,13 @@ class AI {
|
|
|
4087
4160
|
}
|
|
4088
4161
|
}
|
|
4089
4162
|
return {
|
|
4163
|
+
...call,
|
|
4090
4164
|
id:call.id===undefined
|
|
4091
4165
|
?`ollama-${id}-tool-${index+1}`
|
|
4092
4166
|
:call.id,
|
|
4093
4167
|
type:call.type===undefined?'function':call.type,
|
|
4094
4168
|
function:{
|
|
4169
|
+
...nativeFunction,
|
|
4095
4170
|
name:nativeFunction.name,
|
|
4096
4171
|
arguments:encodedArguments
|
|
4097
4172
|
}
|
|
@@ -4100,7 +4175,12 @@ class AI {
|
|
|
4100
4175
|
}
|
|
4101
4176
|
|
|
4102
4177
|
#openAICompatibleOllamaResponse(response={},id=Date.now()){
|
|
4103
|
-
const
|
|
4178
|
+
const responseRecord=isPlainAIRecord(response)?response:{};
|
|
4179
|
+
const message=isPlainAIRecord(responseRecord.message)
|
|
4180
|
+
?responseRecord.message
|
|
4181
|
+
:{};
|
|
4182
|
+
const responseFields={...responseRecord};
|
|
4183
|
+
delete responseFields.message;
|
|
4104
4184
|
const responseId=typeof response?.id==='string'&&response.id
|
|
4105
4185
|
?response.id
|
|
4106
4186
|
:`ollama-${id}`;
|
|
@@ -4111,25 +4191,43 @@ class AI {
|
|
|
4111
4191
|
const toolCalls=normalizeAICompletionToolCalls({
|
|
4112
4192
|
message:{tool_calls:adaptedToolCalls}
|
|
4113
4193
|
});
|
|
4194
|
+
const messageRecord={
|
|
4195
|
+
...message,
|
|
4196
|
+
role:typeof message.role==='string'&&message.role
|
|
4197
|
+
?message.role
|
|
4198
|
+
:'assistant',
|
|
4199
|
+
content:Object.hasOwn(message,'content')?message.content:'',
|
|
4200
|
+
...(Object.hasOwn(message,'tool_calls')||toolCalls.length
|
|
4201
|
+
?{tool_calls:toolCalls}
|
|
4202
|
+
:{})
|
|
4203
|
+
};
|
|
4204
|
+
const usage=isPlainAIRecord(responseRecord.usage)
|
|
4205
|
+
?responseRecord.usage
|
|
4206
|
+
:{};
|
|
4114
4207
|
|
|
4115
4208
|
return {
|
|
4209
|
+
...responseFields,
|
|
4116
4210
|
id:responseId,
|
|
4117
|
-
object:
|
|
4118
|
-
|
|
4119
|
-
|
|
4211
|
+
object:Object.hasOwn(responseRecord,'object')
|
|
4212
|
+
?responseRecord.object
|
|
4213
|
+
:'chat.completion',
|
|
4214
|
+
created:Object.hasOwn(responseRecord,'created')
|
|
4215
|
+
?responseRecord.created
|
|
4216
|
+
:Math.floor(Date.now()/1000),
|
|
4217
|
+
model:Object.hasOwn(responseRecord,'model')
|
|
4218
|
+
?responseRecord.model
|
|
4219
|
+
:this.model,
|
|
4120
4220
|
choices:[
|
|
4121
4221
|
{
|
|
4122
4222
|
index:0,
|
|
4123
|
-
message:
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
},
|
|
4128
|
-
finish_reason:response?.done_reason
|
|
4129
|
-
||(toolCalls.length?'tool_calls':'stop')
|
|
4223
|
+
message:messageRecord,
|
|
4224
|
+
finish_reason:Object.hasOwn(responseRecord,'done_reason')
|
|
4225
|
+
?responseRecord.done_reason
|
|
4226
|
+
:toolCalls.length?'tool_calls':'stop'
|
|
4130
4227
|
}
|
|
4131
4228
|
],
|
|
4132
4229
|
usage:{
|
|
4230
|
+
...usage,
|
|
4133
4231
|
prompt_tokens:Number(response?.prompt_eval_count)||0,
|
|
4134
4232
|
completion_tokens:Number(response?.eval_count)||0,
|
|
4135
4233
|
total_tokens:(Number(response?.prompt_eval_count)||0)
|
|
@@ -4145,12 +4243,14 @@ class AI {
|
|
|
4145
4243
|
localOnly=false,
|
|
4146
4244
|
onChunk=function ignoreStreamChunk(){},
|
|
4147
4245
|
onComplete=function finishIgnoredStream(){},
|
|
4246
|
+
onDataChunk=function ignoreStreamDataChunk(){},
|
|
4247
|
+
onDataResult=function ignoreStreamDataResult(){},
|
|
4148
4248
|
onResponse=function ignoreStreamResponse(){},
|
|
4149
4249
|
tools=[],
|
|
4150
4250
|
toolChoice='auto',
|
|
4151
4251
|
onToolCall=function ignoreEarlyFunction(){},
|
|
4152
4252
|
onRequest=function ignoreStreamRequest(){},
|
|
4153
|
-
parallelToolCalls
|
|
4253
|
+
parallelToolCalls,
|
|
4154
4254
|
id=Date.now(),
|
|
4155
4255
|
seeThinking=false,
|
|
4156
4256
|
signal=null,
|
|
@@ -4182,7 +4282,7 @@ class AI {
|
|
|
4182
4282
|
structuredOutput,
|
|
4183
4283
|
tools,
|
|
4184
4284
|
toolChoice,
|
|
4185
|
-
parallelToolCalls,
|
|
4285
|
+
...(parallelToolCalls!==undefined?{parallelToolCalls}:{}),
|
|
4186
4286
|
id,
|
|
4187
4287
|
seeThinking,
|
|
4188
4288
|
...(maxOutputTokens!==undefined
|
|
@@ -4217,6 +4317,10 @@ class AI {
|
|
|
4217
4317
|
}
|
|
4218
4318
|
);
|
|
4219
4319
|
for await(const chunk of handle){
|
|
4320
|
+
if(signal?.aborted){
|
|
4321
|
+
throw normalizeAIRequestAbort();
|
|
4322
|
+
}
|
|
4323
|
+
await onDataChunk(chunk,id);
|
|
4220
4324
|
if(signal?.aborted){
|
|
4221
4325
|
throw normalizeAIRequestAbort();
|
|
4222
4326
|
}
|
|
@@ -4242,6 +4346,10 @@ class AI {
|
|
|
4242
4346
|
const structuralToolCalls=normalizeAICompletionToolCalls(
|
|
4243
4347
|
completion
|
|
4244
4348
|
);
|
|
4349
|
+
await onDataResult(completion,id);
|
|
4350
|
+
if(signal?.aborted){
|
|
4351
|
+
throw normalizeAIRequestAbort();
|
|
4352
|
+
}
|
|
4245
4353
|
await onResponse(completion,id,false);
|
|
4246
4354
|
for(const call of structuralToolCalls){
|
|
4247
4355
|
if(signal?.aborted){
|
|
@@ -4287,7 +4395,9 @@ class AI {
|
|
|
4287
4395
|
onRequest,
|
|
4288
4396
|
structuredOutput,
|
|
4289
4397
|
false,
|
|
4290
|
-
true
|
|
4398
|
+
true,
|
|
4399
|
+
onDataChunk,
|
|
4400
|
+
onDataResult
|
|
4291
4401
|
);
|
|
4292
4402
|
const structuralToolCalls=normalizeAICompletionToolCalls(
|
|
4293
4403
|
completion
|
|
@@ -4327,13 +4437,15 @@ class AI {
|
|
|
4327
4437
|
tools=[],
|
|
4328
4438
|
tool_choice='auto',
|
|
4329
4439
|
earlyFunctionTrigger=function ignoreEarlyFunction(){},
|
|
4330
|
-
parallel_tool_calls
|
|
4440
|
+
parallel_tool_calls,
|
|
4331
4441
|
id=Date.now(),
|
|
4332
4442
|
seeThinking=false,
|
|
4333
4443
|
signal=null,
|
|
4334
4444
|
requestHandler=function ignoreStreamRequest(){},
|
|
4335
4445
|
structuredOutput=false,
|
|
4336
|
-
returnCompletion=false
|
|
4446
|
+
returnCompletion=false,
|
|
4447
|
+
onDataChunk=function ignoreStreamDataChunk(){},
|
|
4448
|
+
onDataResult=function ignoreStreamDataResult(){}
|
|
4337
4449
|
){
|
|
4338
4450
|
if(this.#shouldUseProviderRuntime('llm',this.llmService,false)){
|
|
4339
4451
|
return this.streamRequest({
|
|
@@ -4342,6 +4454,8 @@ class AI {
|
|
|
4342
4454
|
localOnly:false,
|
|
4343
4455
|
onChunk:streamHandler,
|
|
4344
4456
|
onComplete:streamComplete,
|
|
4457
|
+
onDataChunk,
|
|
4458
|
+
onDataResult,
|
|
4345
4459
|
tools,
|
|
4346
4460
|
toolChoice:tool_choice,
|
|
4347
4461
|
onToolCall:earlyFunctionTrigger,
|
|
@@ -4367,7 +4481,9 @@ class AI {
|
|
|
4367
4481
|
requestHandler,
|
|
4368
4482
|
structuredOutput,
|
|
4369
4483
|
true,
|
|
4370
|
-
returnCompletion
|
|
4484
|
+
returnCompletion,
|
|
4485
|
+
onDataChunk,
|
|
4486
|
+
onDataResult
|
|
4371
4487
|
);
|
|
4372
4488
|
}
|
|
4373
4489
|
|
|
@@ -4378,14 +4494,16 @@ class AI {
|
|
|
4378
4494
|
tools=[],
|
|
4379
4495
|
tool_choice='auto',
|
|
4380
4496
|
earlyFunctionTrigger=function ignoreEarlyFunction(){},
|
|
4381
|
-
parallel_tool_calls
|
|
4497
|
+
parallel_tool_calls,
|
|
4382
4498
|
id=Date.now(),
|
|
4383
4499
|
seeThinking=false,
|
|
4384
4500
|
signal=null,
|
|
4385
4501
|
requestHandler=function ignoreStreamRequest(){},
|
|
4386
4502
|
structuredOutput=false,
|
|
4387
4503
|
finishSpeech=true,
|
|
4388
|
-
returnCompletion=false
|
|
4504
|
+
returnCompletion=false,
|
|
4505
|
+
dataChunkHandler=function ignoreLegacyStreamDataChunk(){},
|
|
4506
|
+
dataResultHandler=function ignoreLegacyStreamDataResult(){}
|
|
4389
4507
|
){
|
|
4390
4508
|
let speechTurnCompleted=false;
|
|
4391
4509
|
|
|
@@ -4420,7 +4538,9 @@ class AI {
|
|
|
4420
4538
|
if(tools.length){
|
|
4421
4539
|
request.tools=tools;
|
|
4422
4540
|
request.tool_choice=tool_choice;
|
|
4423
|
-
|
|
4541
|
+
if(parallel_tool_calls!==undefined){
|
|
4542
|
+
request.parallel_tool_calls=parallel_tool_calls;
|
|
4543
|
+
}
|
|
4424
4544
|
}
|
|
4425
4545
|
|
|
4426
4546
|
if(this.llmService==='OLLAMA'&&this.reasoningEffort){
|
|
@@ -4443,9 +4563,10 @@ class AI {
|
|
|
4443
4563
|
|
|
4444
4564
|
if(nativeOllama){
|
|
4445
4565
|
let nativeContent='';
|
|
4446
|
-
|
|
4566
|
+
let streamedNativeToolCalls=null;
|
|
4447
4567
|
const ollamaTools=this.#ollamaTools(tools,tool_choice);
|
|
4448
4568
|
const ollamaMessages=this.#ollamaMessages(messages,tool_choice);
|
|
4569
|
+
const adaptNativeToolCalls=value=>this.#openAICompatibleOllamaToolCalls(value,id);
|
|
4449
4570
|
const ollamaRequest={
|
|
4450
4571
|
model:this.model,
|
|
4451
4572
|
messages:ollamaMessages,
|
|
@@ -4455,23 +4576,42 @@ class AI {
|
|
|
4455
4576
|
...(ollamaTools.length?{tools:ollamaTools}:{})
|
|
4456
4577
|
};
|
|
4457
4578
|
|
|
4458
|
-
function receiveNativeToolCalls(message={}){
|
|
4459
|
-
const calls=Array.isArray(message.tool_calls)?message.tool_calls:[];
|
|
4460
|
-
|
|
4461
|
-
if(calls.length){
|
|
4462
|
-
isThinking=false;
|
|
4463
|
-
nativeStreamedToolCallFrames.push(calls.slice());
|
|
4464
|
-
}
|
|
4465
|
-
}
|
|
4466
|
-
|
|
4467
4579
|
let nativeChunkPipeline=Promise.resolve();
|
|
4468
4580
|
function queueNativeOllamaChunk(chunk){
|
|
4469
4581
|
nativeChunkPipeline=nativeChunkPipeline.then(
|
|
4470
4582
|
async function processNativeOllamaChunk(){
|
|
4583
|
+
if(signal?.aborted){
|
|
4584
|
+
return;
|
|
4585
|
+
}
|
|
4586
|
+
const dataChunk=projectAIStreamChunk(chunk);
|
|
4587
|
+
if(dataChunk!==OMITTED_AI_STREAM_DATA){
|
|
4588
|
+
await dataChunkHandler(dataChunk,id);
|
|
4589
|
+
}
|
|
4471
4590
|
if(signal?.aborted){
|
|
4472
4591
|
return;
|
|
4473
4592
|
}
|
|
4474
4593
|
const message=chunk?.message||{};
|
|
4594
|
+
if(isPlainAIRecord(message)&&Object.hasOwn(message,'tool_calls')){
|
|
4595
|
+
const observed=normalizeAIStreamToolCallObservation(
|
|
4596
|
+
{message:{tool_calls:adaptNativeToolCalls(message.tool_calls)}},
|
|
4597
|
+
'The native Ollama stream'
|
|
4598
|
+
);
|
|
4599
|
+
if(
|
|
4600
|
+
streamedNativeToolCalls
|
|
4601
|
+
&&(
|
|
4602
|
+
streamedNativeToolCalls.length!==observed.length
|
|
4603
|
+
||streamedNativeToolCalls.some(
|
|
4604
|
+
(call,index)=>!sameAIStructuralToolCall(call,observed[index])
|
|
4605
|
+
)
|
|
4606
|
+
)
|
|
4607
|
+
){
|
|
4608
|
+
throw aiStructuralError(
|
|
4609
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
4610
|
+
'The native Ollama stream changed its complete structural tool calls.'
|
|
4611
|
+
);
|
|
4612
|
+
}
|
|
4613
|
+
streamedNativeToolCalls=observed;
|
|
4614
|
+
}
|
|
4475
4615
|
const thinking=seeThinking
|
|
4476
4616
|
?String(message.thinking||'')
|
|
4477
4617
|
:'';
|
|
@@ -4486,8 +4626,6 @@ class AI {
|
|
|
4486
4626
|
nativeContent+=content;
|
|
4487
4627
|
await streamHandler(content,`M-${id}`,false);
|
|
4488
4628
|
}
|
|
4489
|
-
|
|
4490
|
-
receiveNativeToolCalls(message);
|
|
4491
4629
|
}
|
|
4492
4630
|
);
|
|
4493
4631
|
return nativeChunkPipeline;
|
|
@@ -4519,14 +4657,16 @@ class AI {
|
|
|
4519
4657
|
if(signal?.aborted){
|
|
4520
4658
|
throw normalizeAIRequestAbort();
|
|
4521
4659
|
}
|
|
4660
|
+
const nativeMessage=isPlainAIRecord(nativeResponse?.message)
|
|
4661
|
+
?nativeResponse.message
|
|
4662
|
+
:{};
|
|
4522
4663
|
const nativeCompletion=this.#openAICompatibleOllamaResponse(
|
|
4523
4664
|
{
|
|
4524
4665
|
...nativeResponse,
|
|
4525
4666
|
message:{
|
|
4526
|
-
...
|
|
4527
|
-
content:
|
|
4528
|
-
|
|
4529
|
-
?nativeResponse.message.content
|
|
4667
|
+
...nativeMessage,
|
|
4668
|
+
content:Object.hasOwn(nativeMessage,'content')
|
|
4669
|
+
?nativeMessage.content
|
|
4530
4670
|
:nativeContent
|
|
4531
4671
|
}
|
|
4532
4672
|
},
|
|
@@ -4535,31 +4675,16 @@ class AI {
|
|
|
4535
4675
|
const structuralToolCalls=normalizeAICompletionToolCalls(
|
|
4536
4676
|
nativeCompletion
|
|
4537
4677
|
);
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
{
|
|
4544
|
-
message:{
|
|
4545
|
-
tool_calls:this.#openAICompatibleOllamaToolCalls(
|
|
4546
|
-
nativeStreamedToolCallFrames[frameIndex],
|
|
4547
|
-
id
|
|
4548
|
-
)
|
|
4549
|
-
}
|
|
4550
|
-
},
|
|
4551
|
-
`The native Ollama stream frame ${frameIndex+1}`
|
|
4552
|
-
);
|
|
4553
|
-
assertAIStreamToolCallCorrelation(
|
|
4554
|
-
streamedToolCalls,
|
|
4555
|
-
structuralToolCalls,
|
|
4556
|
-
'The native Ollama stream'
|
|
4557
|
-
);
|
|
4558
|
-
}
|
|
4678
|
+
assertAIStreamToolCallCorrelation(
|
|
4679
|
+
streamedNativeToolCalls??[],
|
|
4680
|
+
structuralToolCalls,
|
|
4681
|
+
'The native Ollama stream'
|
|
4682
|
+
);
|
|
4559
4683
|
this.#assertRequiredOllamaToolCall(
|
|
4560
4684
|
structuralToolCalls,
|
|
4561
4685
|
tool_choice
|
|
4562
4686
|
);
|
|
4687
|
+
await dataResultHandler(nativeCompletion,id);
|
|
4563
4688
|
for(const call of structuralToolCalls){
|
|
4564
4689
|
if(signal?.aborted){
|
|
4565
4690
|
throw normalizeAIRequestAbort();
|
|
@@ -4568,9 +4693,6 @@ class AI {
|
|
|
4568
4693
|
}
|
|
4569
4694
|
|
|
4570
4695
|
const nativeResult=this.#providerCompletionOutput(nativeCompletion);
|
|
4571
|
-
if(structuralToolCalls.length&&!nativeContent){
|
|
4572
|
-
await streamHandler('',`M-${id}`,false);
|
|
4573
|
-
}
|
|
4574
4696
|
if(finishSpeech){
|
|
4575
4697
|
this.finishTTS();
|
|
4576
4698
|
}
|
|
@@ -4615,15 +4737,14 @@ class AI {
|
|
|
4615
4737
|
|
|
4616
4738
|
await this.#assertResponseOK(response);
|
|
4617
4739
|
|
|
4618
|
-
let
|
|
4619
|
-
|
|
4740
|
+
let sseBuffer='';
|
|
4741
|
+
const completeToolCallsByChoice=new Map();
|
|
4620
4742
|
const streamedToolCallsByChoice=new Map();
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
let
|
|
4624
|
-
|
|
4625
|
-
let
|
|
4626
|
-
let streamUsage=null;
|
|
4743
|
+
const streamedChoicesByIndex=new Map();
|
|
4744
|
+
const streamedChoiceOrder=[];
|
|
4745
|
+
let selectedChoiceIndex=null;
|
|
4746
|
+
const streamMetadata={};
|
|
4747
|
+
let receivedSseDone=false;
|
|
4627
4748
|
const decoder = new TextDecoder('utf-8');
|
|
4628
4749
|
//alert(1)
|
|
4629
4750
|
const reader=response.body?.getReader?.();
|
|
@@ -4632,23 +4753,30 @@ class AI {
|
|
|
4632
4753
|
throw new TypeError('Streaming response body is not readable');
|
|
4633
4754
|
}
|
|
4634
4755
|
|
|
4635
|
-
function receiveStreamedToolCalls(toolCalls=[],
|
|
4636
|
-
const streamedToolCalls=streamedToolCallsByChoice.get(
|
|
4756
|
+
function receiveStreamedToolCalls(toolCalls=[],choiceIndex=0){
|
|
4757
|
+
const streamedToolCalls=streamedToolCallsByChoice.get(choiceIndex)
|
|
4637
4758
|
||new Map();
|
|
4638
4759
|
for(let position=0;position<toolCalls.length;position++){
|
|
4639
4760
|
const toolCall=toolCalls[position]||{};
|
|
4640
4761
|
const toolFunction=toolCall.function||{};
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4762
|
+
if(
|
|
4763
|
+
toolCall.index!==undefined
|
|
4764
|
+
&&(!Number.isSafeInteger(toolCall.index)||toolCall.index<0)
|
|
4765
|
+
){
|
|
4766
|
+
throw aiStructuralError(
|
|
4767
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
4768
|
+
`AI stream choice ${choiceIndex} contains an invalid structural tool-call index.`
|
|
4769
|
+
);
|
|
4770
|
+
}
|
|
4771
|
+
const toolIndex=toolCall.index??position;
|
|
4772
|
+
const record=streamedToolCalls.get(toolIndex)||{
|
|
4645
4773
|
arguments:'',
|
|
4646
4774
|
id:'',
|
|
4775
|
+
index:toolIndex,
|
|
4647
4776
|
invalidArguments:false,
|
|
4648
4777
|
invalidIdentity:false,
|
|
4649
4778
|
invalidName:false,
|
|
4650
4779
|
name:'',
|
|
4651
|
-
order:streamedToolCalls.size,
|
|
4652
4780
|
type:''
|
|
4653
4781
|
};
|
|
4654
4782
|
|
|
@@ -4686,111 +4814,224 @@ class AI {
|
|
|
4686
4814
|
record.invalidArguments=true;
|
|
4687
4815
|
}
|
|
4688
4816
|
|
|
4689
|
-
streamedToolCalls.set(
|
|
4817
|
+
streamedToolCalls.set(toolIndex,record);
|
|
4690
4818
|
}
|
|
4691
|
-
streamedToolCallsByChoice.set(
|
|
4819
|
+
streamedToolCallsByChoice.set(choiceIndex,streamedToolCalls);
|
|
4692
4820
|
}
|
|
4693
4821
|
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4822
|
+
function receiveStreamedChoice(choice={},choicePosition=0){
|
|
4823
|
+
if(
|
|
4824
|
+
choice.index!==undefined
|
|
4825
|
+
&&(!Number.isSafeInteger(choice.index)||choice.index<0)
|
|
4826
|
+
){
|
|
4827
|
+
throw aiStructuralError(
|
|
4828
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
4829
|
+
`AI stream choice ${choicePosition+1} has an invalid index.`
|
|
4830
|
+
);
|
|
4831
|
+
}
|
|
4832
|
+
const choiceIndex=choice.index??choicePosition;
|
|
4833
|
+
if(!streamedChoicesByIndex.has(choiceIndex)){
|
|
4834
|
+
streamedChoiceOrder.push(choiceIndex);
|
|
4835
|
+
if(selectedChoiceIndex===null) selectedChoiceIndex=choiceIndex;
|
|
4836
|
+
}
|
|
4837
|
+
const record=streamedChoicesByIndex.get(choiceIndex)||{
|
|
4838
|
+
choice:{index:choiceIndex},
|
|
4839
|
+
message:{role:'assistant'}
|
|
4840
|
+
};
|
|
4841
|
+
for(const [key,value] of Object.entries(choice)){
|
|
4842
|
+
if(key!=='delta'&&key!=='message') record.choice[key]=value;
|
|
4843
|
+
}
|
|
4844
|
+
record.choice.index=choiceIndex;
|
|
4845
|
+
const delta=isPlainAIRecord(choice.delta)?choice.delta:null;
|
|
4846
|
+
const completeMessage=isPlainAIRecord(choice.message)?choice.message:null;
|
|
4847
|
+
for(const [source,replaceText] of [
|
|
4848
|
+
[delta,false],
|
|
4849
|
+
[completeMessage,true]
|
|
4850
|
+
]){
|
|
4851
|
+
if(!source) continue;
|
|
4852
|
+
for(const [key,value] of Object.entries(source)){
|
|
4853
|
+
if(isAIStreamStructuralKey(key)) continue;
|
|
4854
|
+
if(key==='content'||key==='reasoning'||key==='reasoning_content'){
|
|
4855
|
+
if(
|
|
4856
|
+
!replaceText
|
|
4857
|
+
&&typeof value==='string'
|
|
4858
|
+
&&typeof record.message[key]==='string'
|
|
4859
|
+
){
|
|
4860
|
+
record.message[key]+=value;
|
|
4861
|
+
}else{
|
|
4862
|
+
record.message[key]=value;
|
|
4863
|
+
}
|
|
4864
|
+
continue;
|
|
4865
|
+
}
|
|
4866
|
+
record.message[key]=value;
|
|
4700
4867
|
}
|
|
4868
|
+
}
|
|
4869
|
+
if(typeof record.message.role!=='string'||!record.message.role){
|
|
4870
|
+
record.message.role='assistant';
|
|
4871
|
+
}
|
|
4872
|
+
if(!Object.hasOwn(record.message,'content')) record.message.content='';
|
|
4873
|
+
streamedChoicesByIndex.set(choiceIndex,record);
|
|
4874
|
+
return choiceIndex;
|
|
4875
|
+
}
|
|
4701
4876
|
|
|
4702
|
-
|
|
4703
|
-
|
|
4877
|
+
async function receiveStreamedResponse(streamedResponse){
|
|
4878
|
+
if(!isPlainAIRecord(streamedResponse)){
|
|
4879
|
+
throw aiStructuralError(
|
|
4880
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
4881
|
+
'The AI stream returned a non-object event payload.'
|
|
4882
|
+
);
|
|
4883
|
+
}
|
|
4884
|
+
const dataChunk=projectAIStreamChunk(streamedResponse);
|
|
4885
|
+
if(dataChunk!==OMITTED_AI_STREAM_DATA){
|
|
4886
|
+
await dataChunkHandler(dataChunk,id);
|
|
4887
|
+
}
|
|
4888
|
+
for(const [key,value] of Object.entries(streamedResponse)){
|
|
4889
|
+
if(key!=='choices') streamMetadata[key]=value;
|
|
4890
|
+
}
|
|
4891
|
+
const choices=Array.isArray(streamedResponse.choices)
|
|
4892
|
+
?streamedResponse.choices
|
|
4893
|
+
:[];
|
|
4894
|
+
for(let choicePosition=0;choicePosition<choices.length;choicePosition+=1){
|
|
4895
|
+
const choice=choices[choicePosition];
|
|
4896
|
+
if(!isPlainAIRecord(choice)){
|
|
4897
|
+
throw aiStructuralError(
|
|
4898
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
4899
|
+
`AI stream choice ${choicePosition+1} is not an object.`
|
|
4900
|
+
);
|
|
4704
4901
|
}
|
|
4902
|
+
const choiceIndex=receiveStreamedChoice(choice,choicePosition);
|
|
4903
|
+
if(choice.delta!==undefined&&!isPlainAIRecord(choice.delta)){
|
|
4904
|
+
throw aiStructuralError(
|
|
4905
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
4906
|
+
`AI stream choice ${choicePosition+1} contains an invalid delta.`
|
|
4907
|
+
);
|
|
4908
|
+
}
|
|
4909
|
+
if(choice.message!==undefined&&!isPlainAIRecord(choice.message)){
|
|
4910
|
+
throw aiStructuralError(
|
|
4911
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
4912
|
+
`AI stream choice ${choicePosition+1} contains an invalid terminal message.`
|
|
4913
|
+
);
|
|
4914
|
+
}
|
|
4915
|
+
const choiceDelta=choice.delta??{};
|
|
4916
|
+
const choiceMessage=choice.message??null;
|
|
4917
|
+
if(Object.hasOwn(choiceDelta,'tool_calls')){
|
|
4918
|
+
if(!Array.isArray(choiceDelta.tool_calls)){
|
|
4919
|
+
throw aiStructuralError(
|
|
4920
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
4921
|
+
`AI stream choice ${choicePosition+1} contains invalid structural tool-call data.`
|
|
4922
|
+
);
|
|
4923
|
+
}
|
|
4924
|
+
receiveStreamedToolCalls(choiceDelta.tool_calls,choiceIndex);
|
|
4925
|
+
}
|
|
4926
|
+
if(choiceMessage&&Object.hasOwn(choiceMessage,'tool_calls')){
|
|
4927
|
+
if(!Array.isArray(choiceMessage.tool_calls)){
|
|
4928
|
+
throw aiStructuralError(
|
|
4929
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
4930
|
+
`AI stream choice ${choicePosition+1} contains invalid terminal structural tool-call data.`
|
|
4931
|
+
);
|
|
4932
|
+
}
|
|
4933
|
+
completeToolCallsByChoice.set(
|
|
4934
|
+
choiceIndex,
|
|
4935
|
+
normalizeAIStreamToolCallObservation(
|
|
4936
|
+
{message:{tool_calls:choiceMessage.tool_calls}},
|
|
4937
|
+
`AI stream choice ${choicePosition+1} terminal message`
|
|
4938
|
+
)
|
|
4939
|
+
);
|
|
4940
|
+
}
|
|
4941
|
+
const content=typeof choiceDelta.content==='string'
|
|
4942
|
+
?choiceDelta.content
|
|
4943
|
+
:'';
|
|
4944
|
+
let value=content;
|
|
4945
|
+
let reasoning='';
|
|
4946
|
+
if(seeThinking){
|
|
4947
|
+
reasoning=typeof choiceDelta.reasoning_content==='string'
|
|
4948
|
+
?choiceDelta.reasoning_content
|
|
4949
|
+
:typeof choiceDelta.reasoning==='string'
|
|
4950
|
+
?choiceDelta.reasoning
|
|
4951
|
+
:'';
|
|
4952
|
+
}
|
|
4953
|
+
isThinking=Boolean(reasoning);
|
|
4954
|
+
if(reasoning) value=reasoning;
|
|
4955
|
+
if(value) await streamHandler(value,`M-${id}`,isThinking);
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4705
4958
|
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4959
|
+
function sseDataText(eventText){
|
|
4960
|
+
const values=[];
|
|
4961
|
+
for(const line of eventText.split(/\r\n|\r|\n/)){
|
|
4962
|
+
if(!line||line.startsWith(':')) continue;
|
|
4963
|
+
const separator=line.indexOf(':');
|
|
4964
|
+
const field=separator<0?line:line.slice(0,separator);
|
|
4965
|
+
if(field!=='data') continue;
|
|
4966
|
+
let value=separator<0?'':line.slice(separator+1);
|
|
4967
|
+
if(value.startsWith(' ')) value=value.slice(1);
|
|
4968
|
+
values.push(value);
|
|
4969
|
+
}
|
|
4970
|
+
return values.length?values.join('\n'):null;
|
|
4971
|
+
}
|
|
4711
4972
|
|
|
4712
|
-
|
|
4713
|
-
|
|
4973
|
+
async function receiveSseEvent(eventText){
|
|
4974
|
+
const eventData=sseDataText(eventText);
|
|
4975
|
+
if(eventData===null) return false;
|
|
4976
|
+
if(eventData==='[DONE]'){
|
|
4977
|
+
receivedSseDone=true;
|
|
4978
|
+
return true;
|
|
4979
|
+
}
|
|
4980
|
+
if(receivedSseDone) return true;
|
|
4981
|
+
let streamedResponse;
|
|
4982
|
+
try{
|
|
4983
|
+
streamedResponse=JSON.parse(eventData);
|
|
4984
|
+
}catch(cause){
|
|
4985
|
+
throw aiStructuralError(
|
|
4986
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
4987
|
+
'The AI stream returned malformed JSON event data.',
|
|
4988
|
+
cause
|
|
4989
|
+
);
|
|
4990
|
+
}
|
|
4991
|
+
await receiveStreamedResponse(streamedResponse);
|
|
4992
|
+
return false;
|
|
4993
|
+
}
|
|
4714
4994
|
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4995
|
+
async function drainSseBuffer(final=false){
|
|
4996
|
+
while(true){
|
|
4997
|
+
const separator=sseBuffer.match(/(?:\r\n|\r|\n){2}/);
|
|
4998
|
+
if(!separator) break;
|
|
4999
|
+
const eventText=sseBuffer.slice(0,separator.index);
|
|
5000
|
+
sseBuffer=sseBuffer.slice(separator.index+separator[0].length);
|
|
5001
|
+
if(await receiveSseEvent(eventText)){
|
|
5002
|
+
sseBuffer='';
|
|
5003
|
+
return true;
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
5006
|
+
if(final&&sseBuffer.length){
|
|
5007
|
+
const eventText=sseBuffer;
|
|
5008
|
+
sseBuffer='';
|
|
5009
|
+
return receiveSseEvent(eventText);
|
|
5010
|
+
}
|
|
5011
|
+
return receivedSseDone;
|
|
5012
|
+
}
|
|
4719
5013
|
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
}catch{
|
|
4724
|
-
continue;
|
|
4725
|
-
}
|
|
4726
|
-
chunkCache='';
|
|
4727
|
-
if(streamedResponse.id!==undefined){
|
|
4728
|
-
streamId=streamedResponse.id;
|
|
4729
|
-
}
|
|
4730
|
-
if(streamedResponse.object!==undefined){
|
|
4731
|
-
streamObject=streamedResponse.object;
|
|
4732
|
-
}
|
|
4733
|
-
if(streamedResponse.created!==undefined){
|
|
4734
|
-
streamCreated=streamedResponse.created;
|
|
4735
|
-
}
|
|
4736
|
-
if(streamedResponse.model!==undefined){
|
|
4737
|
-
streamModel=streamedResponse.model;
|
|
4738
|
-
}
|
|
4739
|
-
if(streamedResponse.usage!==undefined){
|
|
4740
|
-
streamUsage=streamedResponse.usage;
|
|
4741
|
-
}
|
|
5014
|
+
try{
|
|
5015
|
+
while(!receivedSseDone){
|
|
5016
|
+
const {done,value:chunk}=await reader.read();
|
|
4742
5017
|
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
for(let choicePosition=0;
|
|
4747
|
-
choicePosition<choices.length;
|
|
4748
|
-
choicePosition+=1
|
|
4749
|
-
){
|
|
4750
|
-
const choice=choices[choicePosition]||{};
|
|
4751
|
-
const choiceDelta=choice.delta||{};
|
|
4752
|
-
const toolCalls=choiceDelta.tool_calls;
|
|
4753
|
-
if(toolCalls!==undefined&&!Array.isArray(toolCalls)){
|
|
4754
|
-
throw aiStructuralError(
|
|
4755
|
-
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
4756
|
-
`AI stream choice ${choicePosition+1} contains invalid structural tool-call data.`
|
|
4757
|
-
);
|
|
4758
|
-
}
|
|
4759
|
-
if(toolCalls?.length){
|
|
4760
|
-
receiveStreamedToolCalls(
|
|
4761
|
-
toolCalls,
|
|
4762
|
-
choicePosition
|
|
4763
|
-
);
|
|
4764
|
-
}
|
|
4765
|
-
if(choicePosition!==0){
|
|
4766
|
-
continue;
|
|
4767
|
-
}
|
|
4768
|
-
if(choice.finish_reason!==undefined){
|
|
4769
|
-
streamFinishReason=choice.finish_reason;
|
|
4770
|
-
}
|
|
5018
|
+
if(signal?.aborted){
|
|
5019
|
+
throw normalizeAIRequestAbort();
|
|
5020
|
+
}
|
|
4771
5021
|
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
let reasoning='';
|
|
4775
|
-
if(seeThinking){
|
|
4776
|
-
reasoning=choiceDelta.reasoning||'';
|
|
4777
|
-
}
|
|
4778
|
-
if(reasoning){
|
|
4779
|
-
isThinking=true;
|
|
4780
|
-
value=reasoning;
|
|
4781
|
-
}
|
|
4782
|
-
if(!reasoning&&isThinking){
|
|
4783
|
-
chunkString='';
|
|
4784
|
-
}
|
|
4785
|
-
if(!reasoning){
|
|
4786
|
-
isThinking=false;
|
|
4787
|
-
}
|
|
4788
|
-
if(value){
|
|
4789
|
-
await streamHandler(value,`M-${id}`,isThinking);
|
|
4790
|
-
chunkString+=value;
|
|
4791
|
-
}
|
|
4792
|
-
}
|
|
5022
|
+
if(done){
|
|
5023
|
+
break;
|
|
4793
5024
|
}
|
|
5025
|
+
sseBuffer+=decoder.decode(chunk,{stream:true});
|
|
5026
|
+
if(await drainSseBuffer()) break;
|
|
5027
|
+
}
|
|
5028
|
+
if(receivedSseDone){
|
|
5029
|
+
await reader.cancel('[DONE]').catch(
|
|
5030
|
+
error=>console.error('Arcane SSE reader cleanup failed.',error)
|
|
5031
|
+
);
|
|
5032
|
+
}else{
|
|
5033
|
+
sseBuffer+=decoder.decode();
|
|
5034
|
+
await drainSseBuffer(true);
|
|
4794
5035
|
}
|
|
4795
5036
|
}catch(error){
|
|
4796
5037
|
if(isAIRequestAbort(error,signal)){
|
|
@@ -4802,17 +5043,25 @@ class AI {
|
|
|
4802
5043
|
}
|
|
4803
5044
|
|
|
4804
5045
|
const structuralToolCallsByChoice=new Map();
|
|
4805
|
-
const
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
)
|
|
4810
|
-
|
|
5046
|
+
const toolChoiceIndexes=new Set([
|
|
5047
|
+
...streamedToolCallsByChoice.keys(),
|
|
5048
|
+
...completeToolCallsByChoice.keys()
|
|
5049
|
+
]);
|
|
5050
|
+
for(const choiceIndex of [...toolChoiceIndexes].sort((left,right)=>left-right)){
|
|
5051
|
+
const toolCallRecords=streamedToolCallsByChoice.get(choiceIndex)??new Map();
|
|
4811
5052
|
const orderedToolCalls=[...toolCallRecords.values()].sort(
|
|
4812
5053
|
function sortStreamedToolCalls(a,b){
|
|
4813
|
-
return a.
|
|
5054
|
+
return a.index-b.index;
|
|
4814
5055
|
}
|
|
4815
5056
|
);
|
|
5057
|
+
for(let index=0;index<orderedToolCalls.length;index+=1){
|
|
5058
|
+
if(orderedToolCalls[index].index!==index){
|
|
5059
|
+
throw aiStructuralError(
|
|
5060
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
5061
|
+
`AI stream choice ${choiceIndex} omitted an ordered structural tool-call index.`
|
|
5062
|
+
);
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
4816
5065
|
const rawToolCalls=orderedToolCalls.map(
|
|
4817
5066
|
function completeStreamedToolCall(toolCall,index){
|
|
4818
5067
|
if(
|
|
@@ -4822,7 +5071,7 @@ class AI {
|
|
|
4822
5071
|
){
|
|
4823
5072
|
throw aiStructuralError(
|
|
4824
5073
|
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
4825
|
-
`AI stream choice ${
|
|
5074
|
+
`AI stream choice ${choiceIndex} structural tool call ${index+1} changed an exact field.`
|
|
4826
5075
|
);
|
|
4827
5076
|
}
|
|
4828
5077
|
return {
|
|
@@ -4835,58 +5084,79 @@ class AI {
|
|
|
4835
5084
|
};
|
|
4836
5085
|
}
|
|
4837
5086
|
);
|
|
4838
|
-
|
|
4839
|
-
choicePosition,
|
|
4840
|
-
normalizeAIStreamToolCallObservation(
|
|
5087
|
+
const streamedCalls=normalizeAIStreamToolCallObservation(
|
|
4841
5088
|
{message:{tool_calls:rawToolCalls}},
|
|
4842
|
-
`AI stream choice ${
|
|
4843
|
-
)
|
|
4844
|
-
);
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
5089
|
+
`AI stream choice ${choiceIndex}`
|
|
5090
|
+
);
|
|
5091
|
+
const completeCalls=completeToolCallsByChoice.get(choiceIndex);
|
|
5092
|
+
if(completeCalls!==undefined){
|
|
5093
|
+
if(
|
|
5094
|
+
streamedCalls.length
|
|
5095
|
+
&&(
|
|
5096
|
+
streamedCalls.length!==completeCalls.length
|
|
5097
|
+
||streamedCalls.some(
|
|
5098
|
+
(call,index)=>!sameAICanonicalToolCall(call,completeCalls[index])
|
|
5099
|
+
)
|
|
5100
|
+
)
|
|
5101
|
+
){
|
|
5102
|
+
throw aiStructuralError(
|
|
5103
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
5104
|
+
`AI stream choice ${choiceIndex} changed or omitted its terminal structural tool calls.`
|
|
5105
|
+
);
|
|
5106
|
+
}
|
|
5107
|
+
structuralToolCallsByChoice.set(choiceIndex,completeCalls);
|
|
5108
|
+
}else{
|
|
5109
|
+
structuralToolCallsByChoice.set(choiceIndex,streamedCalls);
|
|
4861
5110
|
}
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
5111
|
+
}
|
|
5112
|
+
const completionChoiceIndexes=[...streamedChoiceOrder];
|
|
5113
|
+
for(const choiceIndex of structuralToolCallsByChoice.keys()){
|
|
5114
|
+
if(!completionChoiceIndexes.includes(choiceIndex)){
|
|
5115
|
+
completionChoiceIndexes.push(choiceIndex);
|
|
4866
5116
|
}
|
|
4867
|
-
completionChoices.push({
|
|
4868
|
-
index:choicePosition,
|
|
4869
|
-
message:{
|
|
4870
|
-
role:'assistant',
|
|
4871
|
-
content:'',
|
|
4872
|
-
...(toolCalls.length?{tool_calls:toolCalls}:{})
|
|
4873
|
-
},
|
|
4874
|
-
finish_reason:toolCalls.length?'tool_calls':'stop'
|
|
4875
|
-
});
|
|
4876
5117
|
}
|
|
5118
|
+
if(!completionChoiceIndexes.length) completionChoiceIndexes.push(0);
|
|
5119
|
+
const selectedTerminalChoiceIndex=selectedChoiceIndex??completionChoiceIndexes[0];
|
|
5120
|
+
const structuralToolCalls=structuralToolCallsByChoice.get(selectedTerminalChoiceIndex)||[];
|
|
5121
|
+
const completionChoices=completionChoiceIndexes
|
|
5122
|
+
.map(function completeLegacyStreamChoice(choicePosition){
|
|
5123
|
+
const retained=streamedChoicesByIndex.get(choicePosition)||{
|
|
5124
|
+
choice:{index:choicePosition},
|
|
5125
|
+
message:{role:'assistant'}
|
|
5126
|
+
};
|
|
5127
|
+
const toolCalls=structuralToolCallsByChoice.get(choicePosition)||[];
|
|
5128
|
+
const finishReason=retained.choice.finish_reason;
|
|
5129
|
+
const hasToolCalls=structuralToolCallsByChoice.has(choicePosition);
|
|
5130
|
+
return {
|
|
5131
|
+
...retained.choice,
|
|
5132
|
+
index:Number.isSafeInteger(retained.choice.index)
|
|
5133
|
+
&&retained.choice.index>=0
|
|
5134
|
+
?retained.choice.index
|
|
5135
|
+
:choicePosition,
|
|
5136
|
+
message:{
|
|
5137
|
+
...retained.message,
|
|
5138
|
+
role:'assistant',
|
|
5139
|
+
content:Object.hasOwn(retained.message,'content')
|
|
5140
|
+
?retained.message.content
|
|
5141
|
+
:'',
|
|
5142
|
+
...(hasToolCalls?{tool_calls:toolCalls}:{})
|
|
5143
|
+
},
|
|
5144
|
+
finish_reason:Object.hasOwn(retained.choice,'finish_reason')
|
|
5145
|
+
?finishReason
|
|
5146
|
+
:toolCalls.length?'tool_calls':'stop'
|
|
5147
|
+
};
|
|
5148
|
+
});
|
|
4877
5149
|
const completion={
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
5150
|
+
...streamMetadata,
|
|
5151
|
+
id:Object.hasOwn(streamMetadata,'id')?streamMetadata.id:`legacy-${id}`,
|
|
5152
|
+
object:Object.hasOwn(streamMetadata,'object')
|
|
5153
|
+
?streamMetadata.object
|
|
4881
5154
|
:'chat.completion',
|
|
4882
|
-
created:
|
|
4883
|
-
?
|
|
5155
|
+
created:Object.hasOwn(streamMetadata,'created')
|
|
5156
|
+
?streamMetadata.created
|
|
4884
5157
|
:Math.floor(Date.now()/1000),
|
|
4885
|
-
model:
|
|
4886
|
-
|
|
4887
|
-
:this.model,
|
|
4888
|
-
choices:completionChoices,
|
|
4889
|
-
...(isPlainAIRecord(streamUsage)?{usage:streamUsage}:{})
|
|
5158
|
+
model:Object.hasOwn(streamMetadata,'model')?streamMetadata.model:this.model,
|
|
5159
|
+
choices:completionChoices
|
|
4890
5160
|
};
|
|
4891
5161
|
const terminalToolCalls=normalizeAICompletionToolCalls(completion);
|
|
4892
5162
|
assertAIStreamToolCallCorrelation(
|
|
@@ -4894,6 +5164,7 @@ class AI {
|
|
|
4894
5164
|
terminalToolCalls,
|
|
4895
5165
|
'The legacy HTTP stream'
|
|
4896
5166
|
);
|
|
5167
|
+
await dataResultHandler(completion,id);
|
|
4897
5168
|
for(const call of terminalToolCalls){
|
|
4898
5169
|
if(signal?.aborted){
|
|
4899
5170
|
throw normalizeAIRequestAbort();
|
|
@@ -4901,9 +5172,6 @@ class AI {
|
|
|
4901
5172
|
await earlyFunctionTrigger(call,`M-${id}`);
|
|
4902
5173
|
}
|
|
4903
5174
|
const streamResult=this.#providerCompletionOutput(completion);
|
|
4904
|
-
if(structuralToolCalls.length&&!chunkString){
|
|
4905
|
-
await streamHandler('',`M-${id}`,false);
|
|
4906
|
-
}
|
|
4907
5175
|
if(finishSpeech){
|
|
4908
5176
|
this.finishTTS();
|
|
4909
5177
|
}
|
|
@@ -4930,7 +5198,7 @@ class AI {
|
|
|
4930
5198
|
localOnly=false,
|
|
4931
5199
|
tools=[],
|
|
4932
5200
|
toolChoice='auto',
|
|
4933
|
-
parallelToolCalls
|
|
5201
|
+
parallelToolCalls,
|
|
4934
5202
|
id=Date.now(),
|
|
4935
5203
|
signal=null,
|
|
4936
5204
|
onRequest=function ignoreFetchRequest(){},
|
|
@@ -4966,7 +5234,7 @@ class AI {
|
|
|
4966
5234
|
structuredOutput,
|
|
4967
5235
|
tools,
|
|
4968
5236
|
toolChoice,
|
|
4969
|
-
parallelToolCalls,
|
|
5237
|
+
...(parallelToolCalls!==undefined?{parallelToolCalls}:{}),
|
|
4970
5238
|
id,
|
|
4971
5239
|
...(maxOutputTokens!==undefined
|
|
4972
5240
|
?{maxTokens:maxOutputTokens}
|
|
@@ -5023,7 +5291,7 @@ class AI {
|
|
|
5023
5291
|
structuredOutput=false,
|
|
5024
5292
|
tools=[],
|
|
5025
5293
|
tool_choice='auto',
|
|
5026
|
-
parallel_tool_calls
|
|
5294
|
+
parallel_tool_calls,
|
|
5027
5295
|
id=Date.now(),
|
|
5028
5296
|
requestHandler=function ignoreFetchRequest(){},
|
|
5029
5297
|
signal=null,
|
|
@@ -5062,7 +5330,7 @@ class AI {
|
|
|
5062
5330
|
structuredOutput=false,
|
|
5063
5331
|
tools=[],
|
|
5064
5332
|
tool_choice='auto',
|
|
5065
|
-
parallel_tool_calls
|
|
5333
|
+
parallel_tool_calls,
|
|
5066
5334
|
id=Date.now(),
|
|
5067
5335
|
requestHandler=function ignoreFetchRequest(){},
|
|
5068
5336
|
signal=null,
|
|
@@ -5095,7 +5363,9 @@ class AI {
|
|
|
5095
5363
|
if(tools.length){
|
|
5096
5364
|
request.tools=tools;
|
|
5097
5365
|
request.tool_choice=tool_choice;
|
|
5098
|
-
|
|
5366
|
+
if(parallel_tool_calls!==undefined){
|
|
5367
|
+
request.parallel_tool_calls=parallel_tool_calls;
|
|
5368
|
+
}
|
|
5099
5369
|
}
|
|
5100
5370
|
|
|
5101
5371
|
if(this.llmService==='OLLAMA'&&this.reasoningEffort){
|
|
@@ -6256,11 +6526,10 @@ function installAIUserReadyRegistration(){
|
|
|
6256
6526
|
});
|
|
6257
6527
|
unsubscribe=arcaneEvents.subscribe(
|
|
6258
6528
|
'user-entity-loaded',
|
|
6259
|
-
function initializeAIFromCanonicalUser(
|
|
6260
|
-
if(event?.detail?.user&&event.detail.user!==window.user)return;
|
|
6529
|
+
function initializeAIFromCanonicalUser(){
|
|
6261
6530
|
if(!window.user?.ready)return;
|
|
6262
6531
|
registration.dispose();
|
|
6263
|
-
instantiateAI(
|
|
6532
|
+
instantiateAI();
|
|
6264
6533
|
}
|
|
6265
6534
|
);
|
|
6266
6535
|
Object.defineProperty(
|
|
@@ -6270,20 +6539,18 @@ function installAIUserReadyRegistration(){
|
|
|
6270
6539
|
value:registration,
|
|
6271
6540
|
configurable:true,
|
|
6272
6541
|
enumerable:false,
|
|
6273
|
-
writable:
|
|
6542
|
+
writable:true
|
|
6274
6543
|
}
|
|
6275
6544
|
);
|
|
6545
|
+
if(window.user?.ready){
|
|
6546
|
+
registration.dispose();
|
|
6547
|
+
instantiateAI();
|
|
6548
|
+
return null;
|
|
6549
|
+
}
|
|
6276
6550
|
return registration;
|
|
6277
6551
|
}
|
|
6278
6552
|
|
|
6279
|
-
function instantiateAI(
|
|
6280
|
-
if(
|
|
6281
|
-
event?.detail?.user
|
|
6282
|
-
&&event.detail.user!==window.user
|
|
6283
|
-
){
|
|
6284
|
-
return;
|
|
6285
|
-
}
|
|
6286
|
-
|
|
6553
|
+
function instantiateAI() {
|
|
6287
6554
|
if(!window.user?.ready){
|
|
6288
6555
|
return;
|
|
6289
6556
|
}
|