arcane-os 0.3.4 → 0.3.6
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 +469 -220
- package/runtime/arcane/components/speech.html +33 -13
- 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 +776 -151
- 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 +23 -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/installed-sdk-runtime.mjs +11 -1
|
@@ -313,10 +313,10 @@ export function completeValueText(value) {
|
|
|
313
313
|
|
|
314
314
|
function textFromCompletion(completion) {
|
|
315
315
|
if (!Array.isArray(completion?.choices)) return completeValueText(completion);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
316
|
+
if(completion.choices.length>1)return completeValueText(completion);
|
|
317
|
+
const choice=completion.choices[0];
|
|
318
|
+
const content=choice?.message?.content;
|
|
319
|
+
return content===undefined?completeValueText(choice):completeValueText(content);
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
function structuralToolCall(call,location){
|
|
@@ -369,9 +369,11 @@ function structuralToolCall(call,location){
|
|
|
369
369
|
);
|
|
370
370
|
}
|
|
371
371
|
return {
|
|
372
|
+
...call,
|
|
372
373
|
id:call.id,
|
|
373
374
|
type:"function",
|
|
374
375
|
function:{
|
|
376
|
+
...call.function,
|
|
375
377
|
name:call.function.name,
|
|
376
378
|
arguments:call.function.arguments,
|
|
377
379
|
},
|
|
@@ -464,15 +466,13 @@ function structuralRequest(value){
|
|
|
464
466
|
throw new TypeError("AI request messages must be an array.");
|
|
465
467
|
}
|
|
466
468
|
requireToolMessageSchemas(value.tools,"AI request tools");
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
{operation:"request"},
|
|
472
|
-
);
|
|
469
|
+
for(const key of ["parallelToolCalls","parallel_tool_calls"]){
|
|
470
|
+
if(Object.hasOwn(value,key)&&typeof value[key]!=="boolean"){
|
|
471
|
+
throw new TypeError(`AI request ${key} must be a boolean when provided.`);
|
|
472
|
+
}
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
|
|
475
|
+
const pendingToolCallIds=new Set();
|
|
476
476
|
for(const [messageIndex,message] of (value.messages??[]).entries()){
|
|
477
477
|
const calls=structuralCallsFromMessage(
|
|
478
478
|
message,
|
|
@@ -487,18 +487,28 @@ function structuralRequest(value){
|
|
|
487
487
|
{operation:"request"},
|
|
488
488
|
);
|
|
489
489
|
}
|
|
490
|
-
if(
|
|
490
|
+
if(pendingToolCallIds.size&&calls.length){
|
|
491
491
|
throw new ArcaneAIError(
|
|
492
|
-
"
|
|
493
|
-
"
|
|
492
|
+
"ARCANE_AI_TOOL_RESULT_REQUIRED",
|
|
493
|
+
"Every pending structural tool result must be supplied before another assistant tool-call sequence.",
|
|
494
494
|
{operation:"request"},
|
|
495
495
|
);
|
|
496
496
|
}
|
|
497
497
|
if(calls.length){
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
498
|
+
for(const [callIndex,call] of calls.entries()){
|
|
499
|
+
const normalized=structuralToolCall(
|
|
500
|
+
call,
|
|
501
|
+
`AI request messages[${String(messageIndex)}].tool_calls[${String(callIndex)}]`,
|
|
502
|
+
);
|
|
503
|
+
if(pendingToolCallIds.has(normalized.id)){
|
|
504
|
+
throw new ArcaneAIError(
|
|
505
|
+
"ARCANE_AI_TOOL_CALL_INVALID",
|
|
506
|
+
`AI request messages[${String(messageIndex)}].tool_calls contains a duplicate ID.`,
|
|
507
|
+
{operation:"request"},
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
pendingToolCallIds.add(normalized.id);
|
|
511
|
+
}
|
|
502
512
|
openedToolCall=true;
|
|
503
513
|
}
|
|
504
514
|
}
|
|
@@ -514,9 +524,9 @@ function structuralRequest(value){
|
|
|
514
524
|
);
|
|
515
525
|
}
|
|
516
526
|
if(
|
|
517
|
-
|
|
527
|
+
!pendingToolCallIds.size
|
|
518
528
|
||typeof message.tool_call_id!=="string"
|
|
519
|
-
|
|
529
|
+
||!pendingToolCallIds.has(message.tool_call_id)
|
|
520
530
|
){
|
|
521
531
|
throw new ArcaneAIError(
|
|
522
532
|
"ARCANE_AI_INVALID_TOOL_MESSAGE",
|
|
@@ -524,8 +534,8 @@ function structuralRequest(value){
|
|
|
524
534
|
{operation:"request"},
|
|
525
535
|
);
|
|
526
536
|
}
|
|
527
|
-
|
|
528
|
-
}else if(
|
|
537
|
+
pendingToolCallIds.delete(message.tool_call_id);
|
|
538
|
+
}else if(pendingToolCallIds.size&&!openedToolCall){
|
|
529
539
|
throw new ArcaneAIError(
|
|
530
540
|
"ARCANE_AI_TOOL_RESULT_REQUIRED",
|
|
531
541
|
`AI request messages[${String(messageIndex)}] precedes the pending structural tool result.`,
|
|
@@ -533,7 +543,7 @@ function structuralRequest(value){
|
|
|
533
543
|
);
|
|
534
544
|
}
|
|
535
545
|
}
|
|
536
|
-
if(
|
|
546
|
+
if(pendingToolCallIds.size){
|
|
537
547
|
throw new ArcaneAIError(
|
|
538
548
|
"ARCANE_AI_TOOL_RESULT_REQUIRED",
|
|
539
549
|
"The pending structural tool call must be settled before requesting another response.",
|
|
@@ -541,14 +551,7 @@ function structuralRequest(value){
|
|
|
541
551
|
);
|
|
542
552
|
}
|
|
543
553
|
|
|
544
|
-
return {
|
|
545
|
-
...value,
|
|
546
|
-
...(value.tools?.length
|
|
547
|
-
&&value.parallelToolCalls===undefined
|
|
548
|
-
&&value.parallel_tool_calls===undefined
|
|
549
|
-
?{parallelToolCalls:false}
|
|
550
|
-
:{}),
|
|
551
|
-
};
|
|
554
|
+
return {...value};
|
|
552
555
|
}
|
|
553
556
|
|
|
554
557
|
function toolRecordFromCompletion(completion) {
|
|
@@ -626,79 +629,353 @@ function toolRecordFromCompletion(completion) {
|
|
|
626
629
|
}
|
|
627
630
|
}
|
|
628
631
|
for (let messageIndex=0;messageIndex<messages.length;messageIndex+=1) {
|
|
632
|
+
const toolCallIds=new Set();
|
|
629
633
|
const calls=structuralCallsFromMessage(
|
|
630
634
|
messages[messageIndex],
|
|
631
635
|
`Structural tool call message ${String(messageIndex+1)}`,
|
|
632
636
|
);
|
|
633
|
-
if(messageIndex>0&&calls?.length){
|
|
634
|
-
throw new ArcaneAIError(
|
|
635
|
-
"ARCANE_AI_INVALID_PROVIDER_RESULT",
|
|
636
|
-
"The model placed a structural tool call outside the selected first choice.",
|
|
637
|
-
{operation:"request"},
|
|
638
|
-
);
|
|
639
|
-
}
|
|
640
637
|
for (const [callIndex,call] of (calls ?? []).entries()) {
|
|
641
|
-
|
|
638
|
+
const normalized=structuralToolCall(
|
|
642
639
|
call,
|
|
643
640
|
`Structural tool call ${String(messageIndex+1)}.${String(callIndex+1)}`,
|
|
644
|
-
)
|
|
641
|
+
);
|
|
642
|
+
if(toolCallIds.has(normalized.id)){
|
|
643
|
+
throw new ArcaneAIError(
|
|
644
|
+
"ARCANE_AI_TOOL_CALL_INVALID",
|
|
645
|
+
"The model completion contains a duplicate structural tool-call ID.",
|
|
646
|
+
{operation:"request"},
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
toolCallIds.add(normalized.id);
|
|
650
|
+
if(messageIndex===0)result.push(normalized);
|
|
645
651
|
}
|
|
646
652
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
653
|
+
return result.length ? result : null;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function sameStreamedToolCalls(left,right){
|
|
657
|
+
return left.length===right.length&&left.every((call,index)=>{
|
|
658
|
+
const other=right[index];
|
|
659
|
+
return call?.id===other?.id
|
|
660
|
+
&&call?.type===other?.type
|
|
661
|
+
&&call?.function?.name===other?.function?.name
|
|
662
|
+
&&call?.function?.arguments===other?.function?.arguments;
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function sameCompleteStreamValue(left,right,leftToRight=new Map(),rightToLeft=new Map()){
|
|
667
|
+
if(Object.is(left,right))return true;
|
|
668
|
+
if(
|
|
669
|
+
!left
|
|
670
|
+
||!right
|
|
671
|
+
||typeof left!=="object"
|
|
672
|
+
||typeof right!=="object"
|
|
673
|
+
||Array.isArray(left)!==Array.isArray(right)
|
|
674
|
+
)return false;
|
|
675
|
+
if(leftToRight.has(left)||rightToLeft.has(right)){
|
|
676
|
+
return leftToRight.get(left)===right&&rightToLeft.get(right)===left;
|
|
677
|
+
}
|
|
678
|
+
leftToRight.set(left,right);
|
|
679
|
+
rightToLeft.set(right,left);
|
|
680
|
+
const leftKeys=Reflect.ownKeys(left);
|
|
681
|
+
const rightKeys=Reflect.ownKeys(right);
|
|
682
|
+
if(leftKeys.length!==rightKeys.length)return false;
|
|
683
|
+
for(const key of leftKeys){
|
|
684
|
+
if(!Object.hasOwn(right,key))return false;
|
|
685
|
+
const leftDescriptor=Object.getOwnPropertyDescriptor(left,key);
|
|
686
|
+
const rightDescriptor=Object.getOwnPropertyDescriptor(right,key);
|
|
687
|
+
const leftIsData=Boolean(leftDescriptor&&Object.hasOwn(leftDescriptor,"value"));
|
|
688
|
+
const rightIsData=Boolean(rightDescriptor&&Object.hasOwn(rightDescriptor,"value"));
|
|
689
|
+
if(leftIsData!==rightIsData)return false;
|
|
690
|
+
if(leftIsData){
|
|
691
|
+
if(!sameCompleteStreamValue(
|
|
692
|
+
leftDescriptor.value,
|
|
693
|
+
rightDescriptor.value,
|
|
694
|
+
leftToRight,
|
|
695
|
+
rightToLeft,
|
|
696
|
+
))return false;
|
|
697
|
+
}else if(
|
|
698
|
+
leftDescriptor?.get!==rightDescriptor?.get
|
|
699
|
+
||leftDescriptor?.set!==rightDescriptor?.set
|
|
700
|
+
)return false;
|
|
701
|
+
}
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function createStreamedToolCallAccumulator(){
|
|
706
|
+
const directChoice=Symbol("direct-stream-choice");
|
|
707
|
+
const choices=new Map();
|
|
708
|
+
|
|
709
|
+
function mismatch(message){
|
|
710
|
+
return new ArcaneAIError(
|
|
711
|
+
"ARCANE_AI_TOOL_CALL_INVALID",
|
|
712
|
+
message,
|
|
651
713
|
{operation:"request"},
|
|
652
714
|
);
|
|
653
715
|
}
|
|
654
|
-
|
|
716
|
+
|
|
717
|
+
function structuralRecord(value){
|
|
718
|
+
return plainStructuralRecord(value)&&[
|
|
719
|
+
"tool_calls",
|
|
720
|
+
"toolCalls",
|
|
721
|
+
"tool_call",
|
|
722
|
+
"toolCall",
|
|
723
|
+
"function_call",
|
|
724
|
+
"functionCall",
|
|
725
|
+
].some((key)=>Object.hasOwn(value,key));
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function choiceState(key){
|
|
729
|
+
let state=choices.get(key);
|
|
730
|
+
if(!state){
|
|
731
|
+
state={
|
|
732
|
+
completeCalls:null,
|
|
733
|
+
fragments:new Map(),
|
|
734
|
+
sawFragments:false,
|
|
735
|
+
};
|
|
736
|
+
choices.set(key,state);
|
|
737
|
+
}
|
|
738
|
+
return state;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function rememberCompleteMessage(state,message,location){
|
|
742
|
+
if(!plainStructuralRecord(message))return;
|
|
743
|
+
const structuralKeys=[
|
|
744
|
+
"tool_calls",
|
|
745
|
+
"toolCalls",
|
|
746
|
+
"tool_call",
|
|
747
|
+
"toolCall",
|
|
748
|
+
"function_call",
|
|
749
|
+
"functionCall",
|
|
750
|
+
];
|
|
751
|
+
if(!structuralKeys.some((key)=>Object.hasOwn(message,key)))return;
|
|
752
|
+
const calls=structuralCallsFromMessage(message,location).map(
|
|
753
|
+
(call,index)=>structuralToolCall(call,`${location}.tool_calls[${String(index)}]`),
|
|
754
|
+
);
|
|
755
|
+
if(state.completeCalls&&!sameCompleteStreamValue(state.completeCalls,calls)){
|
|
756
|
+
throw mismatch("The streamed structural tool-call message changed before completion.");
|
|
757
|
+
}
|
|
758
|
+
state.completeCalls=calls;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function rememberDelta(state,delta,location){
|
|
762
|
+
if(!plainStructuralRecord(delta))return;
|
|
763
|
+
for(const key of ["toolCalls","tool_call","toolCall","function_call","functionCall"]){
|
|
764
|
+
if(Object.hasOwn(delta,key)){
|
|
765
|
+
throw mismatch(`${location} contains a noncanonical structural tool-call field.`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if(!Object.hasOwn(delta,"tool_calls"))return;
|
|
769
|
+
const descriptor=Object.getOwnPropertyDescriptor(delta,"tool_calls");
|
|
770
|
+
if(!descriptor||!Object.hasOwn(descriptor,"value")||!Array.isArray(descriptor.value)){
|
|
771
|
+
throw mismatch(`${location}.tool_calls must be an array data property.`);
|
|
772
|
+
}
|
|
773
|
+
for(const [fragmentPosition,fragment] of descriptor.value.entries()){
|
|
774
|
+
if(
|
|
775
|
+
!plainStructuralRecord(fragment)
|
|
776
|
+
||!Number.isSafeInteger(fragment.index)
|
|
777
|
+
||fragment.index<0
|
|
778
|
+
){
|
|
779
|
+
throw mismatch(
|
|
780
|
+
`${location}.tool_calls[${String(fragmentPosition)}] must have a valid structural call index.`,
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
const current=state.fragments.get(fragment.index)??{
|
|
784
|
+
index:fragment.index,
|
|
785
|
+
id:"",
|
|
786
|
+
type:"",
|
|
787
|
+
name:"",
|
|
788
|
+
arguments:"",
|
|
789
|
+
};
|
|
790
|
+
if(Object.hasOwn(fragment,"id")){
|
|
791
|
+
if(
|
|
792
|
+
typeof fragment.id!=="string"
|
|
793
|
+
||!fragment.id
|
|
794
|
+
||current.id&¤t.id!==fragment.id
|
|
795
|
+
){
|
|
796
|
+
throw mismatch("A streamed structural tool call changed or omitted its ID.");
|
|
797
|
+
}
|
|
798
|
+
current.id=fragment.id;
|
|
799
|
+
}
|
|
800
|
+
if(Object.hasOwn(fragment,"type")){
|
|
801
|
+
if(
|
|
802
|
+
typeof fragment.type!=="string"
|
|
803
|
+
||!fragment.type
|
|
804
|
+
||current.type&¤t.type!==fragment.type
|
|
805
|
+
){
|
|
806
|
+
throw mismatch("A streamed structural tool call changed or omitted its type.");
|
|
807
|
+
}
|
|
808
|
+
current.type=fragment.type;
|
|
809
|
+
}
|
|
810
|
+
if(Object.hasOwn(fragment,"function")){
|
|
811
|
+
if(!plainStructuralRecord(fragment.function)){
|
|
812
|
+
throw mismatch("A streamed structural tool call has an invalid function fragment.");
|
|
813
|
+
}
|
|
814
|
+
if(Object.hasOwn(fragment.function,"name")){
|
|
815
|
+
if(typeof fragment.function.name!=="string"){
|
|
816
|
+
throw mismatch("A streamed structural tool call has an invalid function-name fragment.");
|
|
817
|
+
}
|
|
818
|
+
current.name+=fragment.function.name;
|
|
819
|
+
}
|
|
820
|
+
if(Object.hasOwn(fragment.function,"arguments")){
|
|
821
|
+
if(typeof fragment.function.arguments!=="string"){
|
|
822
|
+
throw mismatch("A streamed structural tool call has an invalid arguments fragment.");
|
|
823
|
+
}
|
|
824
|
+
current.arguments+=fragment.function.arguments;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
state.fragments.set(fragment.index,current);
|
|
828
|
+
state.sawFragments=true;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function observe(chunk){
|
|
833
|
+
if(!plainStructuralRecord(chunk))return;
|
|
834
|
+
if(structuralRecord(chunk.delta)||structuralRecord(chunk.message)){
|
|
835
|
+
const state=choiceState(directChoice);
|
|
836
|
+
rememberDelta(state,chunk.delta,"The streamed model delta");
|
|
837
|
+
rememberCompleteMessage(state,chunk.message,"The streamed model message");
|
|
838
|
+
}
|
|
839
|
+
if(!Array.isArray(chunk.choices)||!chunk.choices.length)return;
|
|
840
|
+
for(const [position,choice] of chunk.choices.entries()){
|
|
841
|
+
if(
|
|
842
|
+
!plainStructuralRecord(choice)
|
|
843
|
+
||(!structuralRecord(choice.delta)&&!structuralRecord(choice.message))
|
|
844
|
+
)continue;
|
|
845
|
+
if(!Number.isSafeInteger(choice.index)||choice.index<0){
|
|
846
|
+
throw mismatch(
|
|
847
|
+
`The streamed model choice ${String(position)} has no valid choice index.`,
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
const state=choiceState(choice.index);
|
|
851
|
+
const location=`The streamed model choice ${String(choice.index)}`;
|
|
852
|
+
rememberDelta(state,choice.delta,`${location} delta`);
|
|
853
|
+
rememberCompleteMessage(state,choice.message,`${location} message`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function fragmentCalls(state){
|
|
858
|
+
if(!state.sawFragments)return null;
|
|
859
|
+
const records=[...state.fragments.values()].sort((left,right)=>left.index-right.index);
|
|
860
|
+
for(let index=0;index<records.length;index+=1){
|
|
861
|
+
if(records[index].index!==index){
|
|
862
|
+
throw mismatch("The streamed structural tool calls omitted an ordered call index.");
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return records.map((record)=>({
|
|
866
|
+
id:record.id,
|
|
867
|
+
type:record.type,
|
|
868
|
+
function:{name:record.name,arguments:record.arguments},
|
|
869
|
+
}));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function correlateChoice(state,terminalCalls){
|
|
873
|
+
const fragmentsResult=fragmentCalls(state);
|
|
874
|
+
if(
|
|
875
|
+
state.completeCalls
|
|
876
|
+
&&fragmentsResult
|
|
877
|
+
&&!sameStreamedToolCalls(state.completeCalls,fragmentsResult)
|
|
878
|
+
){
|
|
879
|
+
throw mismatch("The streamed structural tool-call fragments do not match the streamed complete message.");
|
|
880
|
+
}
|
|
881
|
+
if(state.completeCalls&&!sameCompleteStreamValue(state.completeCalls,terminalCalls)){
|
|
882
|
+
throw mismatch("The complete streamed structural tool calls do not match the terminal completion.");
|
|
883
|
+
}
|
|
884
|
+
if(fragmentsResult&&!sameStreamedToolCalls(fragmentsResult,terminalCalls)){
|
|
885
|
+
throw mismatch("The streamed structural tool-call fragments do not match the terminal completion.");
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function terminalChoices(completion){
|
|
890
|
+
const result=new Map();
|
|
891
|
+
if(typeof completion==="string")return result;
|
|
892
|
+
if(Object.hasOwn(completion,"message")){
|
|
893
|
+
result.set(
|
|
894
|
+
directChoice,
|
|
895
|
+
structuralCallsFromMessage(
|
|
896
|
+
completion.message,
|
|
897
|
+
"The terminal model message",
|
|
898
|
+
).map(
|
|
899
|
+
(call,index)=>structuralToolCall(
|
|
900
|
+
call,
|
|
901
|
+
`The terminal model message.tool_calls[${String(index)}]`,
|
|
902
|
+
),
|
|
903
|
+
),
|
|
904
|
+
);
|
|
905
|
+
return result;
|
|
906
|
+
}
|
|
907
|
+
for(const choice of completion.choices){
|
|
908
|
+
result.set(
|
|
909
|
+
choice.index,
|
|
910
|
+
structuralCallsFromMessage(
|
|
911
|
+
choice.message,
|
|
912
|
+
`The terminal model choice ${String(choice.index)} message`,
|
|
913
|
+
).map(
|
|
914
|
+
(call,index)=>structuralToolCall(
|
|
915
|
+
call,
|
|
916
|
+
`The terminal model choice ${String(choice.index)}.tool_calls[${String(index)}]`,
|
|
917
|
+
),
|
|
918
|
+
),
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
return result;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function correlate(completion){
|
|
925
|
+
const terminal=terminalChoices(completion);
|
|
926
|
+
for(const [key,state] of choices){
|
|
927
|
+
if(!terminal.has(key)){
|
|
928
|
+
throw mismatch(
|
|
929
|
+
"A streamed structural tool-call choice has no matching terminal completion choice.",
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
correlateChoice(state,terminal.get(key));
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
return {observe,correlate};
|
|
655
937
|
}
|
|
656
938
|
|
|
657
|
-
function
|
|
658
|
-
return key==="
|
|
659
|
-
||key==="
|
|
660
|
-
||key==="
|
|
661
|
-
||key==="
|
|
662
|
-
||key==="
|
|
939
|
+
function isPublicStreamStructuralKey(key){
|
|
940
|
+
return key==="tool_calls"
|
|
941
|
+
||key==="toolCalls"
|
|
942
|
+
||key==="tool_call"
|
|
943
|
+
||key==="toolCall"
|
|
944
|
+
||key==="function_call"
|
|
945
|
+
||key==="functionCall";
|
|
663
946
|
}
|
|
664
947
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
948
|
+
const OMITTED_PUBLIC_STREAM_DATA=Symbol("omitted-public-stream-data");
|
|
949
|
+
|
|
950
|
+
function projectPublicStreamData(value,seen=new Map()){
|
|
951
|
+
if(value===null||value===undefined||typeof value!=="object")return value;
|
|
952
|
+
if(seen.has(value))return seen.get(value);
|
|
668
953
|
if(Array.isArray(value)){
|
|
669
954
|
const result=[];
|
|
955
|
+
seen.set(value,result);
|
|
670
956
|
for(const item of value){
|
|
671
|
-
const projected=
|
|
672
|
-
if(projected!==
|
|
957
|
+
const projected=projectPublicStreamData(item,seen);
|
|
958
|
+
if(projected!==OMITTED_PUBLIC_STREAM_DATA)result.push(projected);
|
|
673
959
|
}
|
|
674
|
-
|
|
675
|
-
return result.length?result:null;
|
|
676
|
-
}
|
|
677
|
-
if(!plainStructuralRecord(value)){
|
|
678
|
-
seen.delete(value);
|
|
679
|
-
return null;
|
|
960
|
+
return result.length||value.length===0?result:OMITTED_PUBLIC_STREAM_DATA;
|
|
680
961
|
}
|
|
681
962
|
const result={};
|
|
963
|
+
seen.set(value,result);
|
|
964
|
+
let sourceDataFields=0;
|
|
682
965
|
for(const [key,descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))){
|
|
683
966
|
if(!Object.hasOwn(descriptor,"value"))continue;
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
){
|
|
689
|
-
result[key]=descriptor.value;
|
|
690
|
-
continue;
|
|
691
|
-
}
|
|
692
|
-
const projected=projectPublicStreamContent(descriptor.value,seen);
|
|
693
|
-
if(projected!==null)result[key]=projected;
|
|
967
|
+
sourceDataFields+=1;
|
|
968
|
+
if(isPublicStreamStructuralKey(key))continue;
|
|
969
|
+
const projected=projectPublicStreamData(descriptor.value,seen);
|
|
970
|
+
if(projected!==OMITTED_PUBLIC_STREAM_DATA)result[key]=projected;
|
|
694
971
|
}
|
|
695
|
-
|
|
696
|
-
|
|
972
|
+
return Object.keys(result).length||sourceDataFields===0
|
|
973
|
+
?result
|
|
974
|
+
:OMITTED_PUBLIC_STREAM_DATA;
|
|
697
975
|
}
|
|
698
976
|
|
|
699
977
|
function projectPublicStreamChunk(value){
|
|
700
|
-
|
|
701
|
-
return projectPublicStreamContent(value);
|
|
978
|
+
return projectPublicStreamData(value);
|
|
702
979
|
}
|
|
703
980
|
|
|
704
981
|
export class ModelController {
|
|
@@ -784,8 +1061,8 @@ export class ModelController {
|
|
|
784
1061
|
Object.defineProperty(unsubscribeModelControllerEvent, "dispose", {
|
|
785
1062
|
value: unsubscribeModelControllerEvent,
|
|
786
1063
|
enumerable: false,
|
|
787
|
-
configurable:
|
|
788
|
-
writable:
|
|
1064
|
+
configurable: true,
|
|
1065
|
+
writable: true,
|
|
789
1066
|
});
|
|
790
1067
|
return unsubscribeModelControllerEvent;
|
|
791
1068
|
}
|
|
@@ -1041,6 +1318,28 @@ export class ModelController {
|
|
|
1041
1318
|
let openedIterator = null;
|
|
1042
1319
|
let openError = null;
|
|
1043
1320
|
let cancelPromise = null;
|
|
1321
|
+
const streamedToolCalls=createStreamedToolCallAccumulator();
|
|
1322
|
+
const publicChunks=[];
|
|
1323
|
+
const publicChunkWaiters=[];
|
|
1324
|
+
let publicStreamSettled=false;
|
|
1325
|
+
let publicStreamError=null;
|
|
1326
|
+
|
|
1327
|
+
function publishPublicChunk(value){
|
|
1328
|
+
if(publicStreamSettled)return;
|
|
1329
|
+
const waiter=publicChunkWaiters.shift();
|
|
1330
|
+
if(waiter)waiter.resolve({value,done:false});
|
|
1331
|
+
else publicChunks.push(value);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function settlePublicStream(error=null){
|
|
1335
|
+
if(publicStreamSettled)return;
|
|
1336
|
+
publicStreamSettled=true;
|
|
1337
|
+
publicStreamError=error;
|
|
1338
|
+
for(const waiter of publicChunkWaiters.splice(0)){
|
|
1339
|
+
if(error)waiter.reject(error);
|
|
1340
|
+
else waiter.resolve({value:undefined,done:true});
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1044
1343
|
|
|
1045
1344
|
const openPromise = (async () => {
|
|
1046
1345
|
if (linked.controller.signal.aborted) {
|
|
@@ -1115,9 +1414,41 @@ export class ModelController {
|
|
|
1115
1414
|
});
|
|
1116
1415
|
openPromise.catch(() => undefined);
|
|
1117
1416
|
|
|
1118
|
-
const
|
|
1417
|
+
const privateStreamPump=openPromise.then(async ()=>{
|
|
1418
|
+
const streamIterator=openedIterator;
|
|
1419
|
+
try{
|
|
1420
|
+
while(true){
|
|
1421
|
+
const next=await streamIterator.next();
|
|
1422
|
+
if(next.done){
|
|
1423
|
+
return true;
|
|
1424
|
+
}
|
|
1425
|
+
streamedToolCalls.observe(next.value);
|
|
1426
|
+
const projected=projectPublicStreamChunk(next.value);
|
|
1427
|
+
if(projected!==OMITTED_PUBLIC_STREAM_DATA)publishPublicChunk(projected);
|
|
1428
|
+
}
|
|
1429
|
+
}catch(error){
|
|
1430
|
+
settlePublicStream(error);
|
|
1431
|
+
throw error;
|
|
1432
|
+
}
|
|
1433
|
+
}).catch((error)=>{
|
|
1434
|
+
settlePublicStream(error);
|
|
1435
|
+
throw error;
|
|
1436
|
+
});
|
|
1437
|
+
privateStreamPump.catch(()=>undefined);
|
|
1438
|
+
|
|
1439
|
+
const terminalResult=openPromise.then((value)=>value.result).then((value)=>{
|
|
1119
1440
|
toolRecordFromCompletion(value);
|
|
1120
1441
|
return value;
|
|
1442
|
+
});
|
|
1443
|
+
terminalResult.catch(()=>undefined);
|
|
1444
|
+
|
|
1445
|
+
const result = Promise.all([terminalResult,privateStreamPump]).then(([terminal]) => {
|
|
1446
|
+
streamedToolCalls.correlate(terminal);
|
|
1447
|
+
settlePublicStream();
|
|
1448
|
+
return terminal;
|
|
1449
|
+
}).catch((error)=>{
|
|
1450
|
+
settlePublicStream(error);
|
|
1451
|
+
throw error;
|
|
1121
1452
|
}).finally(() => {
|
|
1122
1453
|
linked.release();
|
|
1123
1454
|
controller.#activeStreams.delete(handle);
|
|
@@ -1129,6 +1460,10 @@ export class ModelController {
|
|
|
1129
1460
|
async cancel(reason = "The stream was cancelled.") {
|
|
1130
1461
|
cancelPromise ||= (async () => {
|
|
1131
1462
|
linked.controller.abort(reason);
|
|
1463
|
+
settlePublicStream(normalizeArcaneAIError(null,{
|
|
1464
|
+
operation:"request",
|
|
1465
|
+
signal:linked.controller.signal,
|
|
1466
|
+
}));
|
|
1132
1467
|
try {
|
|
1133
1468
|
const value = opened ?? await openPromise;
|
|
1134
1469
|
await value.cancel?.(reason);
|
|
@@ -1144,18 +1479,13 @@ export class ModelController {
|
|
|
1144
1479
|
})();
|
|
1145
1480
|
return cancelPromise;
|
|
1146
1481
|
},
|
|
1147
|
-
async next(
|
|
1148
|
-
if (
|
|
1149
|
-
if(
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
nextValue=undefined;
|
|
1155
|
-
if(next.done)return {value:undefined,done:true};
|
|
1156
|
-
const projected=projectPublicStreamChunk(next.value);
|
|
1157
|
-
if(projected!==null)return {value:projected,done:false};
|
|
1158
|
-
}
|
|
1482
|
+
async next() {
|
|
1483
|
+
if(publicChunks.length)return {value:publicChunks.shift(),done:false};
|
|
1484
|
+
if(publicStreamError)throw publicStreamError;
|
|
1485
|
+
if(publicStreamSettled)return {value:undefined,done:true};
|
|
1486
|
+
return new Promise(function waitForProjectedModelStreamChunk(resolve,reject){
|
|
1487
|
+
publicChunkWaiters.push({resolve,reject});
|
|
1488
|
+
});
|
|
1159
1489
|
},
|
|
1160
1490
|
async return(value) {
|
|
1161
1491
|
Promise.resolve().then(()=>this.cancel(
|
|
@@ -1207,12 +1537,22 @@ export class ModelController {
|
|
|
1207
1537
|
try {
|
|
1208
1538
|
for await (const chunk of handle) {
|
|
1209
1539
|
if (options.signal?.aborted) break;
|
|
1210
|
-
|
|
1540
|
+
await options.onDataChunk?.(chunk, id);
|
|
1541
|
+
if(typeof chunk==="string"){
|
|
1542
|
+
if(chunk)await options.onChunk?.(chunk, displayId, false);
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
const streamedChoices=Array.isArray(chunk?.choices)?chunk.choices:[];
|
|
1546
|
+
for(const choice of streamedChoices){
|
|
1211
1547
|
const delta = choice?.delta ?? {};
|
|
1212
|
-
if (
|
|
1548
|
+
if (
|
|
1549
|
+
typeof delta.reasoning_content === "string"
|
|
1550
|
+
&&delta.reasoning_content
|
|
1551
|
+
&&options.seeThinking === true
|
|
1552
|
+
) {
|
|
1213
1553
|
await options.onChunk?.(delta.reasoning_content, displayId, true);
|
|
1214
1554
|
}
|
|
1215
|
-
if (typeof delta.content === "string") {
|
|
1555
|
+
if (typeof delta.content === "string"&&delta.content) {
|
|
1216
1556
|
await options.onChunk?.(delta.content, displayId, false);
|
|
1217
1557
|
}
|
|
1218
1558
|
}
|
|
@@ -1222,13 +1562,17 @@ export class ModelController {
|
|
|
1222
1562
|
throw normalizeArcaneAIError(null, { operation: "request", signal: options.signal });
|
|
1223
1563
|
}
|
|
1224
1564
|
const tools = toolRecordFromCompletion(completion);
|
|
1565
|
+
await options.onDataResult?.(completion, id);
|
|
1225
1566
|
if (typeof options.onResponse === "function") {
|
|
1226
1567
|
await options.onResponse(completion, id, false);
|
|
1227
1568
|
}
|
|
1228
1569
|
for (const call of tools ?? []) {
|
|
1229
1570
|
await options.onToolCall?.(call, displayId);
|
|
1230
1571
|
}
|
|
1231
|
-
const
|
|
1572
|
+
const multipleChoices=Array.isArray(completion?.choices)&&completion.choices.length>1;
|
|
1573
|
+
const output = multipleChoices
|
|
1574
|
+
?textFromCompletion(completion)
|
|
1575
|
+
:tools??textFromCompletion(completion);
|
|
1232
1576
|
if (typeof options.onComplete === "function") {
|
|
1233
1577
|
await options.onComplete(output, displayId, false);
|
|
1234
1578
|
}
|