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.
Files changed (39) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +7 -7
  3. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +315 -119
  4. package/browser-runtime/ai/model-controller.mjs +439 -95
  5. package/package.json +1 -1
  6. package/runtime/arcane/components/assistant-panel.html +2 -1
  7. package/runtime/arcane/components/chat.html +466 -206
  8. package/runtime/arcane/components/speech.html +107 -30
  9. package/runtime/arcane/components/voice-transcription.html +27 -3
  10. package/runtime/arcane/entities/Chat.js +165 -97
  11. package/runtime/arcane/entities/IntentEnvelope.js +52 -118
  12. package/runtime/arcane/entities/TWiNPolicyDecision.js +33 -130
  13. package/runtime/arcane/entities/User.js +38 -44
  14. package/runtime/arcane/modules/AI.js +569 -302
  15. package/runtime/arcane/modules/AIProviderRuntime.js +765 -135
  16. package/runtime/arcane/modules/AIRuntimeState.js +22 -24
  17. package/runtime/arcane/modules/ApiModelDatabase.js +54 -48
  18. package/runtime/arcane/modules/CaseEvidenceIndexer.js +6 -10
  19. package/runtime/arcane/modules/CommunicationHub.js +90 -94
  20. package/runtime/arcane/modules/ComponentContracts.js +34 -9
  21. package/runtime/arcane/modules/ConfiguredAIChatSession.js +77 -77
  22. package/runtime/arcane/modules/ConversationTimebox.js +76 -104
  23. package/runtime/arcane/modules/DBLS.js +14 -12
  24. package/runtime/arcane/modules/DBOPFS.js +20 -15
  25. package/runtime/arcane/modules/Errors.js +196 -436
  26. package/runtime/arcane/modules/HTMLImport.js +49 -32
  27. package/runtime/arcane/modules/Ollama.js +16 -14
  28. package/runtime/arcane/modules/PersistentAIChatSession.js +174 -93
  29. package/runtime/arcane/modules/RecordReviewStore.js +40 -35
  30. package/runtime/arcane/modules/TerminalClient.js +12 -14
  31. package/runtime/arcane/modules/ThemeBootstrap.js +7 -7
  32. package/runtime/arcane/modules/ThemeManager.js +5 -5
  33. package/runtime/arcane/modules/TimeGuard.js +5 -65
  34. package/runtime/arcane/modules/WaitForComponent.js +43 -40
  35. package/src/cli/main.mjs +12 -11
  36. package/src/installed-sdk-runtime.mjs +11 -1
  37. package/src/mail-server.mjs +12 -5
  38. package/src/mail.mjs +25 -19
  39. package/src/workspace.mjs +32 -9
@@ -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
- return completion.choices.map((choice) => {
317
- const content = choice?.message?.content;
318
- return content === undefined ? completeValueText(choice) : completeValueText(content);
319
- }).join("\n");
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
- if(value.parallelToolCalls===true||value.parallel_tool_calls===true){
468
- throw new ArcaneAIError(
469
- "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
470
- "The Arcane chat session accepts one structural tool call at a time.",
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
- let pendingToolCallId=null;
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(calls.length>1||pendingToolCallId!==null&&calls.length){
490
+ if(pendingToolCallIds.size&&calls.length){
491
491
  throw new ArcaneAIError(
492
- "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
493
- "The Arcane chat session accepts one structural tool call at a time.",
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
- pendingToolCallId=structuralToolCall(
499
- calls[0],
500
- `AI request messages[${String(messageIndex)}].tool_calls[0]`,
501
- ).id;
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
- pendingToolCallId===null
527
+ !pendingToolCallIds.size
518
528
  ||typeof message.tool_call_id!=="string"
519
- ||message.tool_call_id!==pendingToolCallId
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
- pendingToolCallId=null;
528
- }else if(pendingToolCallId!==null&&!openedToolCall){
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(pendingToolCallId!==null){
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
- result.push(structuralToolCall(
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
- if(result.length>1){
648
- throw new ArcaneAIError(
649
- "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
650
- "The Arcane chat session accepts one structural tool call at a time.",
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
- return result.length ? result : null;
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&&current.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&&current.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 isPublicStreamContentKey(key){
658
- return key==="content"
659
- ||key==="text"
660
- ||key==="thinking"
661
- ||key==="reasoning"
662
- ||key==="reasoning_content";
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
- function projectPublicStreamContent(value,seen=new WeakSet()){
666
- if(!value||typeof value!=="object"||seen.has(value))return null;
667
- seen.add(value);
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=projectPublicStreamContent(item,seen);
672
- if(projected!==null)result.push(projected);
957
+ const projected=projectPublicStreamData(item,seen);
958
+ if(projected!==OMITTED_PUBLIC_STREAM_DATA)result.push(projected);
673
959
  }
674
- seen.delete(value);
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
- if(
685
- isPublicStreamContentKey(key)
686
- &&descriptor.value!==null
687
- &&descriptor.value!==undefined
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
- seen.delete(value);
696
- return Object.keys(result).length?result:null;
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
- if(typeof value==="string")return value;
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: false,
788
- writable: false,
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 result = openPromise.then((value) => value.result).then((value) => {
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(value) {
1148
- if (openError) throw openError;
1149
- if(!opened)await openPromise;
1150
- const streamIterator=openedIterator;
1151
- let nextValue=value;
1152
- while(true){
1153
- const next=await streamIterator.next(nextValue);
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
- for (const choice of chunk?.choices ?? []) {
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 (typeof delta.reasoning_content === "string" && options.seeThinking === true) {
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 output = tools ?? textFromCompletion(completion);
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
  }