@tanstack/ai-devtools-core 0.0.2 → 0.0.3
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/dist/esm/store/ai-context.js +0 -66
- package/dist/esm/store/ai-context.js.map +1 -1
- package/package.json +2 -2
- package/src/store/ai-context.tsx +818 -954
package/src/store/ai-context.tsx
CHANGED
|
@@ -599,1165 +599,1029 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
599
599
|
// ============= Client Events =============
|
|
600
600
|
|
|
601
601
|
cleanupFns.push(
|
|
602
|
-
aiEventClient.on(
|
|
603
|
-
|
|
604
|
-
(
|
|
605
|
-
|
|
602
|
+
aiEventClient.on('client:created', (e) => {
|
|
603
|
+
const clientId = e.payload.clientId
|
|
604
|
+
getOrCreateConversation(
|
|
605
|
+
clientId,
|
|
606
|
+
'client',
|
|
607
|
+
`Client Chat (${clientId.substring(0, 8)})`,
|
|
608
|
+
)
|
|
609
|
+
updateConversation(clientId, { model: undefined, provider: 'Client' })
|
|
610
|
+
}),
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
cleanupFns.push(
|
|
614
|
+
aiEventClient.on('client:message-sent', (e) => {
|
|
615
|
+
const clientId = e.payload.clientId
|
|
616
|
+
if (!state.conversations[clientId]) {
|
|
606
617
|
getOrCreateConversation(
|
|
607
618
|
clientId,
|
|
608
619
|
'client',
|
|
609
620
|
`Client Chat (${clientId.substring(0, 8)})`,
|
|
610
621
|
)
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
622
|
+
}
|
|
623
|
+
addMessage(clientId, {
|
|
624
|
+
id: e.payload.messageId,
|
|
625
|
+
role: 'user',
|
|
626
|
+
content: e.payload.content,
|
|
627
|
+
timestamp: e.payload.timestamp,
|
|
628
|
+
source: 'client',
|
|
629
|
+
})
|
|
630
|
+
updateConversation(clientId, { status: 'active' })
|
|
631
|
+
}),
|
|
615
632
|
)
|
|
616
633
|
|
|
617
634
|
cleanupFns.push(
|
|
618
|
-
aiEventClient.on(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
`Client Chat (${clientId.substring(0, 8)})`,
|
|
627
|
-
)
|
|
628
|
-
}
|
|
635
|
+
aiEventClient.on('client:message-appended', (e) => {
|
|
636
|
+
const clientId = e.payload.clientId
|
|
637
|
+
const role = e.payload.role
|
|
638
|
+
|
|
639
|
+
if (role === 'user') return
|
|
640
|
+
if (!state.conversations[clientId]) return
|
|
641
|
+
|
|
642
|
+
if (role === 'assistant') {
|
|
629
643
|
addMessage(clientId, {
|
|
630
644
|
id: e.payload.messageId,
|
|
631
|
-
role: '
|
|
632
|
-
content: e.payload.
|
|
645
|
+
role: 'assistant',
|
|
646
|
+
content: e.payload.contentPreview,
|
|
633
647
|
timestamp: e.payload.timestamp,
|
|
634
648
|
source: 'client',
|
|
635
649
|
})
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const role = e.payload.role
|
|
648
|
-
|
|
649
|
-
if (role === 'user') return
|
|
650
|
-
if (!state.conversations[clientId]) return
|
|
651
|
-
|
|
652
|
-
if (role === 'assistant') {
|
|
653
|
-
addMessage(clientId, {
|
|
654
|
-
id: e.payload.messageId,
|
|
655
|
-
role: 'assistant',
|
|
656
|
-
content: e.payload.contentPreview,
|
|
657
|
-
timestamp: e.payload.timestamp,
|
|
658
|
-
source: 'client',
|
|
659
|
-
})
|
|
660
|
-
} else if (role === 'tool') {
|
|
661
|
-
// Tool result message from the LLM
|
|
662
|
-
addMessage(clientId, {
|
|
663
|
-
id: e.payload.messageId,
|
|
664
|
-
role: 'tool',
|
|
665
|
-
content: e.payload.contentPreview,
|
|
666
|
-
timestamp: e.payload.timestamp,
|
|
667
|
-
source: 'client',
|
|
668
|
-
})
|
|
669
|
-
}
|
|
670
|
-
},
|
|
671
|
-
{ withEventTarget: false },
|
|
672
|
-
),
|
|
650
|
+
} else if (role === 'tool') {
|
|
651
|
+
// Tool result message from the LLM
|
|
652
|
+
addMessage(clientId, {
|
|
653
|
+
id: e.payload.messageId,
|
|
654
|
+
role: 'tool',
|
|
655
|
+
content: e.payload.contentPreview,
|
|
656
|
+
timestamp: e.payload.timestamp,
|
|
657
|
+
source: 'client',
|
|
658
|
+
})
|
|
659
|
+
}
|
|
660
|
+
}),
|
|
673
661
|
)
|
|
674
662
|
|
|
675
663
|
cleanupFns.push(
|
|
676
|
-
aiEventClient.on(
|
|
677
|
-
|
|
678
|
-
(
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
},
|
|
686
|
-
{ withEventTarget: false },
|
|
687
|
-
),
|
|
664
|
+
aiEventClient.on('client:loading-changed', (e) => {
|
|
665
|
+
const clientId = e.payload.clientId
|
|
666
|
+
if (state.conversations[clientId]) {
|
|
667
|
+
updateConversation(clientId, {
|
|
668
|
+
status: e.payload.isLoading ? 'active' : 'completed',
|
|
669
|
+
})
|
|
670
|
+
}
|
|
671
|
+
}),
|
|
688
672
|
)
|
|
689
673
|
|
|
690
674
|
cleanupFns.push(
|
|
691
|
-
aiEventClient.on(
|
|
692
|
-
|
|
693
|
-
(
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
}
|
|
701
|
-
},
|
|
702
|
-
{ withEventTarget: false },
|
|
703
|
-
),
|
|
675
|
+
aiEventClient.on('client:stopped', (e) => {
|
|
676
|
+
const clientId = e.payload.clientId
|
|
677
|
+
if (state.conversations[clientId]) {
|
|
678
|
+
updateConversation(clientId, {
|
|
679
|
+
status: 'completed',
|
|
680
|
+
completedAt: e.payload.timestamp,
|
|
681
|
+
})
|
|
682
|
+
}
|
|
683
|
+
}),
|
|
704
684
|
)
|
|
705
685
|
|
|
706
686
|
cleanupFns.push(
|
|
707
|
-
aiEventClient.on(
|
|
708
|
-
|
|
709
|
-
(
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
},
|
|
719
|
-
{ withEventTarget: false },
|
|
720
|
-
),
|
|
687
|
+
aiEventClient.on('client:messages-cleared', (e) => {
|
|
688
|
+
const clientId = e.payload.clientId
|
|
689
|
+
if (state.conversations[clientId]) {
|
|
690
|
+
updateConversation(clientId, {
|
|
691
|
+
messages: [],
|
|
692
|
+
chunks: [],
|
|
693
|
+
usage: undefined,
|
|
694
|
+
})
|
|
695
|
+
}
|
|
696
|
+
}),
|
|
721
697
|
)
|
|
722
698
|
|
|
723
699
|
cleanupFns.push(
|
|
724
|
-
aiEventClient.on(
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
735
|
-
},
|
|
736
|
-
{ withEventTarget: false },
|
|
737
|
-
),
|
|
700
|
+
aiEventClient.on('client:reloaded', (e) => {
|
|
701
|
+
const clientId = e.payload.clientId
|
|
702
|
+
const conv = state.conversations[clientId]
|
|
703
|
+
if (conv) {
|
|
704
|
+
updateConversation(clientId, {
|
|
705
|
+
messages: conv.messages.slice(0, e.payload.fromMessageIndex),
|
|
706
|
+
status: 'active',
|
|
707
|
+
})
|
|
708
|
+
}
|
|
709
|
+
}),
|
|
738
710
|
)
|
|
739
711
|
|
|
740
712
|
cleanupFns.push(
|
|
741
|
-
aiEventClient.on(
|
|
742
|
-
|
|
743
|
-
(e)
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
},
|
|
749
|
-
{ withEventTarget: false },
|
|
750
|
-
),
|
|
713
|
+
aiEventClient.on('client:error-changed', (e) => {
|
|
714
|
+
const clientId = e.payload.clientId
|
|
715
|
+
if (state.conversations[clientId] && e.payload.error) {
|
|
716
|
+
updateConversation(clientId, { status: 'error' })
|
|
717
|
+
}
|
|
718
|
+
}),
|
|
751
719
|
)
|
|
752
720
|
|
|
753
721
|
cleanupFns.push(
|
|
754
|
-
aiEventClient.on(
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const messageId = e.payload.messageId
|
|
759
|
-
const content = e.payload.content
|
|
722
|
+
aiEventClient.on('client:assistant-message-updated', (e) => {
|
|
723
|
+
const clientId = e.payload.clientId
|
|
724
|
+
const messageId = e.payload.messageId
|
|
725
|
+
const content = e.payload.content
|
|
760
726
|
|
|
761
|
-
|
|
727
|
+
if (!state.conversations[clientId]) return
|
|
762
728
|
|
|
763
|
-
|
|
729
|
+
const conv = state.conversations[clientId]
|
|
764
730
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
731
|
+
// Find message by ID anywhere in the list, not just the last one
|
|
732
|
+
const messageIndex = conv.messages.findIndex(
|
|
733
|
+
(m: Message) => m.id === messageId,
|
|
734
|
+
)
|
|
769
735
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
{ withEventTarget: false },
|
|
780
|
-
),
|
|
736
|
+
// Only update existing messages, don't create new ones
|
|
737
|
+
// (client:message-appended is responsible for creating messages)
|
|
738
|
+
if (messageIndex >= 0) {
|
|
739
|
+
updateMessage(clientId, messageIndex, {
|
|
740
|
+
content,
|
|
741
|
+
model: conv.model,
|
|
742
|
+
})
|
|
743
|
+
}
|
|
744
|
+
}),
|
|
781
745
|
)
|
|
782
746
|
|
|
783
747
|
cleanupFns.push(
|
|
784
|
-
aiEventClient.on(
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
timestamp: number
|
|
802
|
-
}
|
|
748
|
+
aiEventClient.on('client:tool-call-updated', (e) => {
|
|
749
|
+
const {
|
|
750
|
+
clientId,
|
|
751
|
+
messageId,
|
|
752
|
+
toolCallId,
|
|
753
|
+
toolName,
|
|
754
|
+
state: toolCallState,
|
|
755
|
+
arguments: args,
|
|
756
|
+
} = e.payload as {
|
|
757
|
+
clientId: string
|
|
758
|
+
messageId: string
|
|
759
|
+
toolCallId: string
|
|
760
|
+
toolName: string
|
|
761
|
+
state: string
|
|
762
|
+
arguments: unknown
|
|
763
|
+
timestamp: number
|
|
764
|
+
}
|
|
803
765
|
|
|
804
|
-
|
|
766
|
+
if (!state.conversations[clientId]) return
|
|
805
767
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
768
|
+
const conv = state.conversations[clientId]
|
|
769
|
+
const messageIndex = conv.messages.findIndex(
|
|
770
|
+
(m: Message) => m.id === messageId,
|
|
771
|
+
)
|
|
772
|
+
if (messageIndex === -1) return
|
|
811
773
|
|
|
812
|
-
|
|
813
|
-
|
|
774
|
+
const message = conv.messages[messageIndex]
|
|
775
|
+
if (!message) return
|
|
814
776
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
777
|
+
const toolCalls = message.toolCalls || []
|
|
778
|
+
const existingToolIndex = toolCalls.findIndex(
|
|
779
|
+
(t: ToolCall) => t.id === toolCallId,
|
|
780
|
+
)
|
|
819
781
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
782
|
+
const toolCall: ToolCall = {
|
|
783
|
+
id: toolCallId,
|
|
784
|
+
name: toolName,
|
|
785
|
+
arguments: JSON.stringify(args, null, 2),
|
|
786
|
+
state: toolCallState,
|
|
787
|
+
}
|
|
826
788
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
{ withEventTarget: false },
|
|
834
|
-
),
|
|
789
|
+
if (existingToolIndex >= 0) {
|
|
790
|
+
updateToolCall(clientId, messageIndex, existingToolIndex, toolCall)
|
|
791
|
+
} else {
|
|
792
|
+
setToolCalls(clientId, messageIndex, [...toolCalls, toolCall])
|
|
793
|
+
}
|
|
794
|
+
}),
|
|
835
795
|
)
|
|
836
796
|
|
|
837
797
|
cleanupFns.push(
|
|
838
|
-
aiEventClient.on(
|
|
839
|
-
|
|
840
|
-
(e) => {
|
|
841
|
-
const { clientId, messageId, toolCallId, approvalId } = e.payload
|
|
798
|
+
aiEventClient.on('client:approval-requested', (e) => {
|
|
799
|
+
const { clientId, messageId, toolCallId, approvalId } = e.payload
|
|
842
800
|
|
|
843
|
-
|
|
801
|
+
if (!state.conversations[clientId]) return
|
|
844
802
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
)
|
|
849
|
-
if (messageIndex === -1) return
|
|
803
|
+
const conv = state.conversations[clientId]
|
|
804
|
+
const messageIndex = conv.messages.findIndex((m) => m.id === messageId)
|
|
805
|
+
if (messageIndex === -1) return
|
|
850
806
|
|
|
851
|
-
|
|
852
|
-
|
|
807
|
+
const message = conv.messages[messageIndex]
|
|
808
|
+
if (!message?.toolCalls) return
|
|
853
809
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
{ withEventTarget: false },
|
|
866
|
-
),
|
|
810
|
+
const toolCallIndex = message.toolCalls.findIndex(
|
|
811
|
+
(t) => t.id === toolCallId,
|
|
812
|
+
)
|
|
813
|
+
if (toolCallIndex === -1) return
|
|
814
|
+
|
|
815
|
+
updateToolCall(clientId, messageIndex, toolCallIndex, {
|
|
816
|
+
approvalRequired: true,
|
|
817
|
+
approvalId,
|
|
818
|
+
state: 'approval-requested',
|
|
819
|
+
})
|
|
820
|
+
}),
|
|
867
821
|
)
|
|
868
822
|
|
|
869
823
|
// ============= Tool Events =============
|
|
870
824
|
|
|
871
825
|
cleanupFns.push(
|
|
872
|
-
aiEventClient.on(
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
)
|
|
894
|
-
const message = conv.messages[messageIndex]
|
|
895
|
-
if (!message?.toolCalls) continue
|
|
826
|
+
aiEventClient.on('tool:result-added', (e) => {
|
|
827
|
+
const {
|
|
828
|
+
clientId,
|
|
829
|
+
toolCallId,
|
|
830
|
+
toolName,
|
|
831
|
+
output,
|
|
832
|
+
state: resultState,
|
|
833
|
+
timestamp,
|
|
834
|
+
} = e.payload
|
|
835
|
+
|
|
836
|
+
if (!state.conversations[clientId]) return
|
|
837
|
+
|
|
838
|
+
const conv = state.conversations[clientId]
|
|
839
|
+
|
|
840
|
+
// Find the message with the tool call and update it
|
|
841
|
+
for (
|
|
842
|
+
let messageIndex = conv.messages.length - 1;
|
|
843
|
+
messageIndex >= 0;
|
|
844
|
+
messageIndex--
|
|
845
|
+
) {
|
|
846
|
+
const message = conv.messages[messageIndex]
|
|
847
|
+
if (!message?.toolCalls) continue
|
|
896
848
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
849
|
+
const toolCallIndex = message.toolCalls.findIndex(
|
|
850
|
+
(t: ToolCall) => t.id === toolCallId,
|
|
851
|
+
)
|
|
852
|
+
if (toolCallIndex >= 0) {
|
|
853
|
+
// Update the tool call state
|
|
854
|
+
updateToolCall(clientId, messageIndex, toolCallIndex, {
|
|
855
|
+
result: output,
|
|
856
|
+
state: resultState === 'output-error' ? 'error' : 'complete',
|
|
857
|
+
})
|
|
906
858
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
}
|
|
918
|
-
addChunkToMessage(clientId, chunk)
|
|
919
|
-
return
|
|
859
|
+
// Also add a chunk to show the tool result in the chunks view
|
|
860
|
+
const chunk: Chunk = {
|
|
861
|
+
id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
|
|
862
|
+
type: 'tool_result',
|
|
863
|
+
messageId: message.id,
|
|
864
|
+
toolCallId,
|
|
865
|
+
toolName,
|
|
866
|
+
result: output,
|
|
867
|
+
timestamp,
|
|
868
|
+
chunkCount: 1,
|
|
920
869
|
}
|
|
870
|
+
addChunkToMessage(clientId, chunk)
|
|
871
|
+
return
|
|
921
872
|
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
),
|
|
873
|
+
}
|
|
874
|
+
}),
|
|
925
875
|
)
|
|
926
876
|
|
|
927
877
|
cleanupFns.push(
|
|
928
|
-
aiEventClient.on(
|
|
929
|
-
|
|
930
|
-
(e) => {
|
|
931
|
-
const { clientId, toolCallId, approved } = e.payload
|
|
878
|
+
aiEventClient.on('tool:approval-responded', (e) => {
|
|
879
|
+
const { clientId, toolCallId, approved } = e.payload
|
|
932
880
|
|
|
933
|
-
|
|
881
|
+
if (!state.conversations[clientId]) return
|
|
934
882
|
|
|
935
|
-
|
|
883
|
+
const conv = state.conversations[clientId]
|
|
936
884
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
885
|
+
for (
|
|
886
|
+
let messageIndex = conv.messages.length - 1;
|
|
887
|
+
messageIndex >= 0;
|
|
888
|
+
messageIndex--
|
|
889
|
+
) {
|
|
890
|
+
const message = conv.messages[messageIndex]
|
|
891
|
+
if (!message?.toolCalls) continue
|
|
944
892
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
}
|
|
893
|
+
const toolCallIndex = message.toolCalls.findIndex(
|
|
894
|
+
(t: ToolCall) => t.id === toolCallId,
|
|
895
|
+
)
|
|
896
|
+
if (toolCallIndex >= 0) {
|
|
897
|
+
updateToolCall(clientId, messageIndex, toolCallIndex, {
|
|
898
|
+
state: approved ? 'approved' : 'denied',
|
|
899
|
+
approvalRequired: false,
|
|
900
|
+
})
|
|
901
|
+
return
|
|
955
902
|
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
),
|
|
903
|
+
}
|
|
904
|
+
}),
|
|
959
905
|
)
|
|
960
906
|
|
|
961
907
|
// ============= Stream Events =============
|
|
962
908
|
|
|
963
909
|
cleanupFns.push(
|
|
964
|
-
aiEventClient.on(
|
|
965
|
-
|
|
966
|
-
(
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
chunkCount: 1,
|
|
979
|
-
}
|
|
910
|
+
aiEventClient.on('stream:chunk:content', (e) => {
|
|
911
|
+
const streamId = e.payload.streamId
|
|
912
|
+
const conversationId = streamToConversation.get(streamId)
|
|
913
|
+
if (!conversationId) return
|
|
914
|
+
|
|
915
|
+
const chunk: Chunk = {
|
|
916
|
+
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
917
|
+
type: 'content',
|
|
918
|
+
messageId: e.payload.messageId,
|
|
919
|
+
content: e.payload.content,
|
|
920
|
+
delta: e.payload.delta,
|
|
921
|
+
timestamp: e.payload.timestamp,
|
|
922
|
+
chunkCount: 1,
|
|
923
|
+
}
|
|
980
924
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
{ withEventTarget: false },
|
|
994
|
-
),
|
|
925
|
+
const conv = state.conversations[conversationId]
|
|
926
|
+
if (conv?.type === 'client') {
|
|
927
|
+
addChunkToMessage(conversationId, chunk)
|
|
928
|
+
} else {
|
|
929
|
+
ensureMessageForChunk(
|
|
930
|
+
conversationId,
|
|
931
|
+
e.payload.messageId,
|
|
932
|
+
e.payload.timestamp,
|
|
933
|
+
)
|
|
934
|
+
addChunk(conversationId, chunk)
|
|
935
|
+
}
|
|
936
|
+
}),
|
|
995
937
|
)
|
|
996
938
|
|
|
997
939
|
cleanupFns.push(
|
|
998
|
-
aiEventClient.on(
|
|
999
|
-
|
|
1000
|
-
(
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
chunkCount: 1,
|
|
1014
|
-
}
|
|
940
|
+
aiEventClient.on('stream:chunk:tool-call', (e) => {
|
|
941
|
+
const streamId = e.payload.streamId
|
|
942
|
+
const conversationId = streamToConversation.get(streamId)
|
|
943
|
+
if (!conversationId) return
|
|
944
|
+
|
|
945
|
+
const chunk: Chunk = {
|
|
946
|
+
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
947
|
+
type: 'tool_call',
|
|
948
|
+
messageId: e.payload.messageId,
|
|
949
|
+
toolCallId: e.payload.toolCallId,
|
|
950
|
+
toolName: e.payload.toolName,
|
|
951
|
+
arguments: e.payload.arguments,
|
|
952
|
+
timestamp: e.payload.timestamp,
|
|
953
|
+
chunkCount: 1,
|
|
954
|
+
}
|
|
1015
955
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
{ withEventTarget: false },
|
|
1029
|
-
),
|
|
956
|
+
const conv = state.conversations[conversationId]
|
|
957
|
+
if (conv?.type === 'client') {
|
|
958
|
+
addChunkToMessage(conversationId, chunk)
|
|
959
|
+
} else {
|
|
960
|
+
ensureMessageForChunk(
|
|
961
|
+
conversationId,
|
|
962
|
+
e.payload.messageId,
|
|
963
|
+
e.payload.timestamp,
|
|
964
|
+
)
|
|
965
|
+
addChunk(conversationId, chunk)
|
|
966
|
+
}
|
|
967
|
+
}),
|
|
1030
968
|
)
|
|
1031
969
|
|
|
1032
970
|
cleanupFns.push(
|
|
1033
|
-
aiEventClient.on(
|
|
1034
|
-
|
|
1035
|
-
(
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
chunkCount: 1,
|
|
1048
|
-
}
|
|
971
|
+
aiEventClient.on('stream:chunk:tool-result', (e) => {
|
|
972
|
+
const streamId = e.payload.streamId
|
|
973
|
+
const conversationId = streamToConversation.get(streamId)
|
|
974
|
+
if (!conversationId) return
|
|
975
|
+
|
|
976
|
+
const chunk: Chunk = {
|
|
977
|
+
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
978
|
+
type: 'tool_result',
|
|
979
|
+
messageId: e.payload.messageId,
|
|
980
|
+
toolCallId: e.payload.toolCallId,
|
|
981
|
+
content: e.payload.result,
|
|
982
|
+
timestamp: e.payload.timestamp,
|
|
983
|
+
chunkCount: 1,
|
|
984
|
+
}
|
|
1049
985
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
986
|
+
const conv = state.conversations[conversationId]
|
|
987
|
+
if (conv?.type === 'client') {
|
|
988
|
+
addChunkToMessage(conversationId, chunk)
|
|
989
|
+
} else {
|
|
990
|
+
ensureMessageForChunk(
|
|
991
|
+
conversationId,
|
|
992
|
+
e.payload.messageId,
|
|
993
|
+
e.payload.timestamp,
|
|
994
|
+
)
|
|
995
|
+
addChunk(conversationId, chunk)
|
|
996
|
+
}
|
|
1061
997
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
}
|
|
998
|
+
// Also update the toolCalls array with the result
|
|
999
|
+
if (conv && e.payload.toolCallId) {
|
|
1000
|
+
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1001
|
+
const message = conv.messages[i]
|
|
1002
|
+
if (!message?.toolCalls) continue
|
|
1003
|
+
|
|
1004
|
+
const toolCallIndex = message.toolCalls.findIndex(
|
|
1005
|
+
(t) => t.id === e.payload.toolCallId,
|
|
1006
|
+
)
|
|
1007
|
+
if (toolCallIndex >= 0) {
|
|
1008
|
+
updateToolCall(conversationId, i, toolCallIndex, {
|
|
1009
|
+
result: e.payload.result,
|
|
1010
|
+
state: 'complete',
|
|
1011
|
+
})
|
|
1012
|
+
break
|
|
1078
1013
|
}
|
|
1079
1014
|
}
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
),
|
|
1015
|
+
}
|
|
1016
|
+
}),
|
|
1083
1017
|
)
|
|
1084
1018
|
|
|
1085
1019
|
cleanupFns.push(
|
|
1086
|
-
aiEventClient.on(
|
|
1087
|
-
|
|
1088
|
-
(
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
chunkCount: 1,
|
|
1101
|
-
}
|
|
1020
|
+
aiEventClient.on('stream:chunk:thinking', (e) => {
|
|
1021
|
+
const streamId = e.payload.streamId
|
|
1022
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1023
|
+
if (!conversationId) return
|
|
1024
|
+
|
|
1025
|
+
const chunk: Chunk = {
|
|
1026
|
+
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
1027
|
+
type: 'thinking',
|
|
1028
|
+
messageId: e.payload.messageId,
|
|
1029
|
+
content: e.payload.content,
|
|
1030
|
+
delta: e.payload.delta,
|
|
1031
|
+
timestamp: e.payload.timestamp,
|
|
1032
|
+
chunkCount: 1,
|
|
1033
|
+
}
|
|
1102
1034
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
)
|
|
1111
|
-
if (messageIndex !== -1) {
|
|
1112
|
-
updateMessage(conversationId, messageIndex, {
|
|
1113
|
-
thinkingContent: e.payload.content,
|
|
1114
|
-
})
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
} else {
|
|
1118
|
-
ensureMessageForChunk(
|
|
1119
|
-
conversationId,
|
|
1120
|
-
e.payload.messageId,
|
|
1121
|
-
e.payload.timestamp,
|
|
1035
|
+
const conv = state.conversations[conversationId]
|
|
1036
|
+
if (conv?.type === 'client') {
|
|
1037
|
+
addChunkToMessage(conversationId, chunk)
|
|
1038
|
+
|
|
1039
|
+
if (e.payload.messageId) {
|
|
1040
|
+
const messageIndex = conv.messages.findIndex(
|
|
1041
|
+
(msg) => msg.id === e.payload.messageId,
|
|
1122
1042
|
)
|
|
1123
|
-
|
|
1043
|
+
if (messageIndex !== -1) {
|
|
1044
|
+
updateMessage(conversationId, messageIndex, {
|
|
1045
|
+
thinkingContent: e.payload.content,
|
|
1046
|
+
})
|
|
1047
|
+
}
|
|
1124
1048
|
}
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1049
|
+
} else {
|
|
1050
|
+
ensureMessageForChunk(
|
|
1051
|
+
conversationId,
|
|
1052
|
+
e.payload.messageId,
|
|
1053
|
+
e.payload.timestamp,
|
|
1054
|
+
)
|
|
1055
|
+
addChunk(conversationId, chunk)
|
|
1056
|
+
}
|
|
1057
|
+
}),
|
|
1128
1058
|
)
|
|
1129
1059
|
|
|
1130
1060
|
cleanupFns.push(
|
|
1131
|
-
aiEventClient.on(
|
|
1132
|
-
|
|
1133
|
-
(
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
chunkCount: 1,
|
|
1145
|
-
}
|
|
1061
|
+
aiEventClient.on('stream:chunk:done', (e) => {
|
|
1062
|
+
const streamId = e.payload.streamId
|
|
1063
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1064
|
+
if (!conversationId) return
|
|
1065
|
+
|
|
1066
|
+
const chunk: Chunk = {
|
|
1067
|
+
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
1068
|
+
type: 'done',
|
|
1069
|
+
messageId: e.payload.messageId,
|
|
1070
|
+
finishReason: e.payload.finishReason || undefined,
|
|
1071
|
+
timestamp: e.payload.timestamp,
|
|
1072
|
+
chunkCount: 1,
|
|
1073
|
+
}
|
|
1146
1074
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1075
|
+
if (e.payload.usage) {
|
|
1076
|
+
updateConversation(conversationId, { usage: e.payload.usage })
|
|
1077
|
+
updateMessageUsage(
|
|
1078
|
+
conversationId,
|
|
1079
|
+
e.payload.messageId,
|
|
1080
|
+
e.payload.usage,
|
|
1081
|
+
)
|
|
1082
|
+
}
|
|
1155
1083
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1084
|
+
const conv = state.conversations[conversationId]
|
|
1085
|
+
if (conv?.type === 'client') {
|
|
1086
|
+
addChunkToMessage(conversationId, chunk)
|
|
1087
|
+
} else {
|
|
1088
|
+
ensureMessageForChunk(
|
|
1089
|
+
conversationId,
|
|
1090
|
+
e.payload.messageId,
|
|
1091
|
+
e.payload.timestamp,
|
|
1092
|
+
)
|
|
1093
|
+
addChunk(conversationId, chunk)
|
|
1094
|
+
}
|
|
1167
1095
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
{ withEventTarget: false },
|
|
1174
|
-
),
|
|
1096
|
+
updateConversation(conversationId, {
|
|
1097
|
+
status: 'completed',
|
|
1098
|
+
completedAt: e.payload.timestamp,
|
|
1099
|
+
})
|
|
1100
|
+
}),
|
|
1175
1101
|
)
|
|
1176
1102
|
|
|
1177
1103
|
cleanupFns.push(
|
|
1178
|
-
aiEventClient.on(
|
|
1179
|
-
|
|
1180
|
-
(
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
chunkCount: 1,
|
|
1192
|
-
}
|
|
1104
|
+
aiEventClient.on('stream:chunk:error', (e) => {
|
|
1105
|
+
const streamId = e.payload.streamId
|
|
1106
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1107
|
+
if (!conversationId) return
|
|
1108
|
+
|
|
1109
|
+
const chunk: Chunk = {
|
|
1110
|
+
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
1111
|
+
type: 'error',
|
|
1112
|
+
messageId: e.payload.messageId,
|
|
1113
|
+
error: e.payload.error,
|
|
1114
|
+
timestamp: e.payload.timestamp,
|
|
1115
|
+
chunkCount: 1,
|
|
1116
|
+
}
|
|
1193
1117
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1118
|
+
const conv = state.conversations[conversationId]
|
|
1119
|
+
if (conv?.type === 'client') {
|
|
1120
|
+
addChunkToMessage(conversationId, chunk)
|
|
1121
|
+
} else {
|
|
1122
|
+
ensureMessageForChunk(
|
|
1123
|
+
conversationId,
|
|
1124
|
+
e.payload.messageId,
|
|
1125
|
+
e.payload.timestamp,
|
|
1126
|
+
)
|
|
1127
|
+
addChunk(conversationId, chunk)
|
|
1128
|
+
}
|
|
1205
1129
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
{ withEventTarget: false },
|
|
1212
|
-
),
|
|
1130
|
+
updateConversation(conversationId, {
|
|
1131
|
+
status: 'error',
|
|
1132
|
+
completedAt: e.payload.timestamp,
|
|
1133
|
+
})
|
|
1134
|
+
}),
|
|
1213
1135
|
)
|
|
1214
1136
|
|
|
1215
1137
|
cleanupFns.push(
|
|
1216
|
-
aiEventClient.on(
|
|
1217
|
-
|
|
1218
|
-
(
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
})
|
|
1227
|
-
},
|
|
1228
|
-
{ withEventTarget: false },
|
|
1229
|
-
),
|
|
1138
|
+
aiEventClient.on('stream:ended', (e) => {
|
|
1139
|
+
const streamId = e.payload.streamId
|
|
1140
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1141
|
+
if (!conversationId) return
|
|
1142
|
+
|
|
1143
|
+
updateConversation(conversationId, {
|
|
1144
|
+
status: 'completed',
|
|
1145
|
+
completedAt: e.payload.timestamp,
|
|
1146
|
+
})
|
|
1147
|
+
}),
|
|
1230
1148
|
)
|
|
1231
1149
|
|
|
1232
1150
|
cleanupFns.push(
|
|
1233
|
-
aiEventClient.on(
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
const conversationId = streamToConversation.get(streamId)
|
|
1247
|
-
if (!conversationId) return
|
|
1248
|
-
|
|
1249
|
-
const conv = state.conversations[conversationId]
|
|
1250
|
-
if (!conv) return
|
|
1251
|
-
|
|
1252
|
-
const chunk: Chunk = {
|
|
1253
|
-
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
1254
|
-
type: 'approval',
|
|
1255
|
-
messageId: messageId,
|
|
1256
|
-
toolCallId,
|
|
1257
|
-
toolName,
|
|
1258
|
-
approvalId,
|
|
1259
|
-
input,
|
|
1260
|
-
timestamp,
|
|
1261
|
-
chunkCount: 1,
|
|
1262
|
-
}
|
|
1151
|
+
aiEventClient.on('stream:approval-requested', (e) => {
|
|
1152
|
+
const {
|
|
1153
|
+
streamId,
|
|
1154
|
+
messageId,
|
|
1155
|
+
toolCallId,
|
|
1156
|
+
toolName,
|
|
1157
|
+
input,
|
|
1158
|
+
approvalId,
|
|
1159
|
+
timestamp,
|
|
1160
|
+
} = e.payload
|
|
1161
|
+
|
|
1162
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1163
|
+
if (!conversationId) return
|
|
1263
1164
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
}
|
|
1165
|
+
const conv = state.conversations[conversationId]
|
|
1166
|
+
if (!conv) return
|
|
1167
|
+
|
|
1168
|
+
const chunk: Chunk = {
|
|
1169
|
+
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
1170
|
+
type: 'approval',
|
|
1171
|
+
messageId: messageId,
|
|
1172
|
+
toolCallId,
|
|
1173
|
+
toolName,
|
|
1174
|
+
approvalId,
|
|
1175
|
+
input,
|
|
1176
|
+
timestamp,
|
|
1177
|
+
chunkCount: 1,
|
|
1178
|
+
}
|
|
1269
1179
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1180
|
+
if (conv.type === 'client') {
|
|
1181
|
+
addChunkToMessage(conversationId, chunk)
|
|
1182
|
+
} else {
|
|
1183
|
+
addChunk(conversationId, chunk)
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1187
|
+
const message = conv.messages[i]
|
|
1188
|
+
if (!message) continue
|
|
1189
|
+
|
|
1190
|
+
if (message.role === 'assistant' && message.toolCalls) {
|
|
1191
|
+
const toolCallIndex = message.toolCalls.findIndex(
|
|
1192
|
+
(t: ToolCall) => t.id === toolCallId,
|
|
1193
|
+
)
|
|
1194
|
+
if (toolCallIndex >= 0) {
|
|
1195
|
+
updateToolCall(conversationId, i, toolCallIndex, {
|
|
1196
|
+
approvalRequired: true,
|
|
1197
|
+
approvalId,
|
|
1198
|
+
state: 'approval-requested',
|
|
1199
|
+
})
|
|
1200
|
+
return
|
|
1286
1201
|
}
|
|
1287
1202
|
}
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
),
|
|
1203
|
+
}
|
|
1204
|
+
}),
|
|
1291
1205
|
)
|
|
1292
1206
|
|
|
1293
1207
|
// ============= Processor Events =============
|
|
1294
1208
|
|
|
1295
1209
|
cleanupFns.push(
|
|
1296
|
-
aiEventClient.on(
|
|
1297
|
-
|
|
1298
|
-
(e) => {
|
|
1299
|
-
const streamId = e.payload.streamId
|
|
1210
|
+
aiEventClient.on('processor:text-updated', (e) => {
|
|
1211
|
+
const streamId = e.payload.streamId
|
|
1300
1212
|
|
|
1301
|
-
|
|
1213
|
+
let conversationId = streamToConversation.get(streamId)
|
|
1302
1214
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1215
|
+
if (!conversationId) {
|
|
1216
|
+
const activeClients = Object.values(state.conversations)
|
|
1217
|
+
.filter((c) => c.type === 'client' && c.status === 'active')
|
|
1218
|
+
.sort((a, b) => b.startedAt - a.startedAt)
|
|
1307
1219
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
}
|
|
1220
|
+
if (activeClients.length > 0 && activeClients[0]) {
|
|
1221
|
+
conversationId = activeClients[0].id
|
|
1222
|
+
streamToConversation.set(streamId, conversationId)
|
|
1312
1223
|
}
|
|
1224
|
+
}
|
|
1313
1225
|
|
|
1314
|
-
|
|
1226
|
+
if (!conversationId) return
|
|
1315
1227
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1228
|
+
const conv = state.conversations[conversationId]
|
|
1229
|
+
if (!conv) return
|
|
1318
1230
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
{ withEventTarget: false },
|
|
1329
|
-
),
|
|
1231
|
+
// Only update existing assistant messages, don't create new ones
|
|
1232
|
+
// (client:message-appended is responsible for creating messages)
|
|
1233
|
+
const lastMessage = conv.messages[conv.messages.length - 1]
|
|
1234
|
+
if (lastMessage && lastMessage.role === 'assistant') {
|
|
1235
|
+
updateMessage(conversationId, conv.messages.length - 1, {
|
|
1236
|
+
content: e.payload.content,
|
|
1237
|
+
})
|
|
1238
|
+
}
|
|
1239
|
+
}),
|
|
1330
1240
|
)
|
|
1331
1241
|
|
|
1332
1242
|
cleanupFns.push(
|
|
1333
|
-
aiEventClient.on(
|
|
1334
|
-
|
|
1335
|
-
(
|
|
1336
|
-
const streamId = e.payload.streamId
|
|
1337
|
-
const conversationId = streamToConversation.get(streamId)
|
|
1243
|
+
aiEventClient.on('processor:tool-call-state-changed', (e) => {
|
|
1244
|
+
const streamId = e.payload.streamId
|
|
1245
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1338
1246
|
|
|
1339
|
-
|
|
1247
|
+
if (!conversationId || !state.conversations[conversationId]) return
|
|
1340
1248
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1249
|
+
const conv = state.conversations[conversationId]
|
|
1250
|
+
const lastMessage = conv.messages[conv.messages.length - 1]
|
|
1343
1251
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1252
|
+
if (lastMessage && lastMessage.role === 'assistant') {
|
|
1253
|
+
const toolCalls = lastMessage.toolCalls || []
|
|
1254
|
+
const existingToolIndex = toolCalls.findIndex(
|
|
1255
|
+
(t) => t.id === e.payload.toolCallId,
|
|
1256
|
+
)
|
|
1349
1257
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1258
|
+
const toolCall: ToolCall = {
|
|
1259
|
+
id: e.payload.toolCallId,
|
|
1260
|
+
name: e.payload.toolName,
|
|
1261
|
+
arguments: JSON.stringify(e.payload.arguments, null, 2),
|
|
1262
|
+
state: e.payload.state,
|
|
1263
|
+
}
|
|
1356
1264
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
}
|
|
1265
|
+
if (existingToolIndex >= 0) {
|
|
1266
|
+
updateToolCall(
|
|
1267
|
+
conversationId,
|
|
1268
|
+
conv.messages.length - 1,
|
|
1269
|
+
existingToolIndex,
|
|
1270
|
+
toolCall,
|
|
1271
|
+
)
|
|
1272
|
+
} else {
|
|
1273
|
+
setToolCalls(conversationId, conv.messages.length - 1, [
|
|
1274
|
+
...toolCalls,
|
|
1275
|
+
toolCall,
|
|
1276
|
+
])
|
|
1370
1277
|
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
),
|
|
1278
|
+
}
|
|
1279
|
+
}),
|
|
1374
1280
|
)
|
|
1375
1281
|
|
|
1376
1282
|
cleanupFns.push(
|
|
1377
|
-
aiEventClient.on(
|
|
1378
|
-
|
|
1379
|
-
(
|
|
1380
|
-
const streamId = e.payload.streamId
|
|
1381
|
-
const conversationId = streamToConversation.get(streamId)
|
|
1283
|
+
aiEventClient.on('processor:tool-result-state-changed', (e) => {
|
|
1284
|
+
const streamId = e.payload.streamId
|
|
1285
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1382
1286
|
|
|
1383
|
-
|
|
1287
|
+
if (!conversationId || !state.conversations[conversationId]) return
|
|
1384
1288
|
|
|
1385
|
-
|
|
1289
|
+
const conv = state.conversations[conversationId]
|
|
1386
1290
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1291
|
+
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1292
|
+
const message = conv.messages[i]
|
|
1293
|
+
if (!message?.toolCalls) continue
|
|
1390
1294
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
}
|
|
1295
|
+
const toolCallIndex = message.toolCalls.findIndex(
|
|
1296
|
+
(t) => t.id === e.payload.toolCallId,
|
|
1297
|
+
)
|
|
1298
|
+
if (toolCallIndex >= 0) {
|
|
1299
|
+
updateToolCall(conversationId, i, toolCallIndex, {
|
|
1300
|
+
result: e.payload.content,
|
|
1301
|
+
state: e.payload.error ? 'error' : e.payload.state,
|
|
1302
|
+
})
|
|
1303
|
+
return
|
|
1401
1304
|
}
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
),
|
|
1305
|
+
}
|
|
1306
|
+
}),
|
|
1405
1307
|
)
|
|
1406
1308
|
|
|
1407
1309
|
// ============= Chat Events (for usage tracking) =============
|
|
1408
1310
|
|
|
1409
1311
|
cleanupFns.push(
|
|
1410
|
-
aiEventClient.on(
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
return
|
|
1422
|
-
}
|
|
1312
|
+
aiEventClient.on('chat:started', (e) => {
|
|
1313
|
+
const streamId = e.payload.streamId
|
|
1314
|
+
const model = e.payload.model
|
|
1315
|
+
const provider = e.payload.provider
|
|
1316
|
+
const clientId = e.payload.clientId
|
|
1317
|
+
|
|
1318
|
+
if (clientId && state.conversations[clientId]) {
|
|
1319
|
+
streamToConversation.set(streamId, clientId)
|
|
1320
|
+
updateConversation(clientId, { status: 'active', ...e.payload })
|
|
1321
|
+
return
|
|
1322
|
+
}
|
|
1423
1323
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1324
|
+
const activeClient = Object.values(state.conversations).find(
|
|
1325
|
+
(c) => c.type === 'client' && c.status === 'active' && !c.model,
|
|
1326
|
+
)
|
|
1327
|
+
|
|
1328
|
+
if (activeClient) {
|
|
1329
|
+
streamToConversation.set(streamId, activeClient.id)
|
|
1330
|
+
updateConversation(activeClient.id, { ...e.payload })
|
|
1331
|
+
} else {
|
|
1332
|
+
const existingServerConv = Object.values(state.conversations).find(
|
|
1333
|
+
(c) => c.type === 'server' && c.model === model,
|
|
1426
1334
|
)
|
|
1427
1335
|
|
|
1428
|
-
if (
|
|
1429
|
-
streamToConversation.set(streamId,
|
|
1430
|
-
updateConversation(
|
|
1336
|
+
if (existingServerConv) {
|
|
1337
|
+
streamToConversation.set(streamId, existingServerConv.id)
|
|
1338
|
+
updateConversation(existingServerConv.id, {
|
|
1339
|
+
status: 'active',
|
|
1340
|
+
...e.payload,
|
|
1341
|
+
})
|
|
1431
1342
|
} else {
|
|
1432
|
-
const
|
|
1433
|
-
|
|
1434
|
-
)
|
|
1435
|
-
|
|
1436
|
-
if (existingServerConv) {
|
|
1437
|
-
streamToConversation.set(streamId, existingServerConv.id)
|
|
1438
|
-
updateConversation(existingServerConv.id, {
|
|
1439
|
-
status: 'active',
|
|
1440
|
-
...e.payload,
|
|
1441
|
-
})
|
|
1442
|
-
} else {
|
|
1443
|
-
const serverId = `server-${model}`
|
|
1444
|
-
getOrCreateConversation(serverId, 'server', `${model} Server`)
|
|
1445
|
-
streamToConversation.set(streamId, serverId)
|
|
1446
|
-
updateConversation(serverId, { ...e.payload })
|
|
1447
|
-
}
|
|
1343
|
+
const serverId = `server-${model}`
|
|
1344
|
+
getOrCreateConversation(serverId, 'server', `${model} Server`)
|
|
1345
|
+
streamToConversation.set(streamId, serverId)
|
|
1346
|
+
updateConversation(serverId, { ...e.payload })
|
|
1448
1347
|
}
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
),
|
|
1348
|
+
}
|
|
1349
|
+
}),
|
|
1452
1350
|
)
|
|
1453
1351
|
|
|
1454
1352
|
cleanupFns.push(
|
|
1455
|
-
aiEventClient.on(
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
status: 'completed',
|
|
1464
|
-
completedAt: e.payload.timestamp,
|
|
1465
|
-
}
|
|
1466
|
-
if (usage) {
|
|
1467
|
-
updates.usage = usage
|
|
1468
|
-
}
|
|
1469
|
-
updateConversation(conversationId, updates)
|
|
1470
|
-
if (usage) {
|
|
1471
|
-
updateMessageUsage(conversationId, e.payload.messageId, usage)
|
|
1472
|
-
}
|
|
1353
|
+
aiEventClient.on('chat:completed', (e) => {
|
|
1354
|
+
const { requestId, usage } = e.payload
|
|
1355
|
+
|
|
1356
|
+
const conversationId = requestToConversation.get(requestId)
|
|
1357
|
+
if (conversationId && state.conversations[conversationId]) {
|
|
1358
|
+
const updates: Partial<Conversation> = {
|
|
1359
|
+
status: 'completed',
|
|
1360
|
+
completedAt: e.payload.timestamp,
|
|
1473
1361
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1362
|
+
if (usage) {
|
|
1363
|
+
updates.usage = usage
|
|
1364
|
+
}
|
|
1365
|
+
updateConversation(conversationId, updates)
|
|
1366
|
+
if (usage) {
|
|
1367
|
+
updateMessageUsage(conversationId, e.payload.messageId, usage)
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}),
|
|
1477
1371
|
)
|
|
1478
1372
|
|
|
1479
1373
|
cleanupFns.push(
|
|
1480
|
-
aiEventClient.on(
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
}
|
|
1491
|
-
},
|
|
1492
|
-
{ withEventTarget: false },
|
|
1493
|
-
),
|
|
1374
|
+
aiEventClient.on('chat:iteration', (e) => {
|
|
1375
|
+
const { requestId, iterationNumber } = e.payload
|
|
1376
|
+
|
|
1377
|
+
const conversationId = requestToConversation.get(requestId)
|
|
1378
|
+
if (conversationId && state.conversations[conversationId]) {
|
|
1379
|
+
updateConversation(conversationId, {
|
|
1380
|
+
iterationCount: iterationNumber,
|
|
1381
|
+
})
|
|
1382
|
+
}
|
|
1383
|
+
}),
|
|
1494
1384
|
)
|
|
1495
1385
|
|
|
1496
1386
|
cleanupFns.push(
|
|
1497
|
-
aiEventClient.on(
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
}
|
|
1507
|
-
},
|
|
1508
|
-
{ withEventTarget: false },
|
|
1509
|
-
),
|
|
1387
|
+
aiEventClient.on('usage:tokens', (e) => {
|
|
1388
|
+
const { requestId, usage, messageId } = e.payload
|
|
1389
|
+
|
|
1390
|
+
const conversationId = requestToConversation.get(requestId)
|
|
1391
|
+
if (conversationId && state.conversations[conversationId]) {
|
|
1392
|
+
updateConversation(conversationId, { usage })
|
|
1393
|
+
updateMessageUsage(conversationId, messageId, usage)
|
|
1394
|
+
}
|
|
1395
|
+
}),
|
|
1510
1396
|
)
|
|
1511
1397
|
|
|
1512
1398
|
// ============= Tool Call Completed (with duration) =============
|
|
1513
1399
|
|
|
1514
1400
|
cleanupFns.push(
|
|
1515
|
-
aiEventClient.on(
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
const conversationId = streamToConversation.get(streamId)
|
|
1529
|
-
if (!conversationId || !state.conversations[conversationId]) return
|
|
1530
|
-
|
|
1531
|
-
const conv = state.conversations[conversationId]
|
|
1532
|
-
|
|
1533
|
-
// Add a tool_result chunk to show the result in the chunks view
|
|
1534
|
-
const chunk: Chunk = {
|
|
1535
|
-
id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
|
|
1536
|
-
type: 'tool_result',
|
|
1537
|
-
messageId: messageId,
|
|
1538
|
-
toolCallId,
|
|
1539
|
-
toolName,
|
|
1540
|
-
result,
|
|
1541
|
-
duration,
|
|
1542
|
-
timestamp,
|
|
1543
|
-
chunkCount: 1,
|
|
1544
|
-
}
|
|
1401
|
+
aiEventClient.on('tool:call-completed', (e) => {
|
|
1402
|
+
const {
|
|
1403
|
+
streamId,
|
|
1404
|
+
toolCallId,
|
|
1405
|
+
toolName,
|
|
1406
|
+
result,
|
|
1407
|
+
duration,
|
|
1408
|
+
messageId,
|
|
1409
|
+
timestamp,
|
|
1410
|
+
} = e.payload
|
|
1411
|
+
|
|
1412
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1413
|
+
if (!conversationId || !state.conversations[conversationId]) return
|
|
1545
1414
|
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1415
|
+
const conv = state.conversations[conversationId]
|
|
1416
|
+
|
|
1417
|
+
// Add a tool_result chunk to show the result in the chunks view
|
|
1418
|
+
const chunk: Chunk = {
|
|
1419
|
+
id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
|
|
1420
|
+
type: 'tool_result',
|
|
1421
|
+
messageId: messageId,
|
|
1422
|
+
toolCallId,
|
|
1423
|
+
toolName,
|
|
1424
|
+
result,
|
|
1425
|
+
duration,
|
|
1426
|
+
timestamp,
|
|
1427
|
+
chunkCount: 1,
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// Add chunk to message if it's a client conversation, otherwise to conversation
|
|
1431
|
+
if (conv.type === 'client' && messageId) {
|
|
1432
|
+
const messageIndex = conv.messages.findIndex(
|
|
1433
|
+
(m) => m.id === messageId,
|
|
1434
|
+
)
|
|
1435
|
+
if (messageIndex !== -1) {
|
|
1436
|
+
queueMessageChunk(conversationId, messageIndex, chunk)
|
|
1437
|
+
} else {
|
|
1438
|
+
// If message not found, add to last assistant message
|
|
1439
|
+
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1440
|
+
if (conv.messages[i]?.role === 'assistant') {
|
|
1441
|
+
queueMessageChunk(conversationId, i, chunk)
|
|
1442
|
+
break
|
|
1560
1443
|
}
|
|
1561
1444
|
}
|
|
1562
|
-
} else {
|
|
1563
|
-
addChunk(conversationId, chunk)
|
|
1564
1445
|
}
|
|
1446
|
+
} else {
|
|
1447
|
+
addChunk(conversationId, chunk)
|
|
1448
|
+
}
|
|
1565
1449
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1450
|
+
// Update the tool call with duration
|
|
1451
|
+
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1452
|
+
const message = conv.messages[i]
|
|
1453
|
+
if (!message?.toolCalls) continue
|
|
1570
1454
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
}
|
|
1455
|
+
const toolCallIndex = message.toolCalls.findIndex(
|
|
1456
|
+
(t) => t.id === toolCallId,
|
|
1457
|
+
)
|
|
1458
|
+
if (toolCallIndex >= 0) {
|
|
1459
|
+
updateToolCall(conversationId, i, toolCallIndex, {
|
|
1460
|
+
duration,
|
|
1461
|
+
result,
|
|
1462
|
+
})
|
|
1463
|
+
return
|
|
1581
1464
|
}
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
),
|
|
1465
|
+
}
|
|
1466
|
+
}),
|
|
1585
1467
|
)
|
|
1586
1468
|
|
|
1587
1469
|
// ============= Embedding Events =============
|
|
1588
1470
|
|
|
1589
1471
|
cleanupFns.push(
|
|
1590
|
-
aiEventClient.on(
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
//
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
`Embedding (${model})`,
|
|
1613
|
-
)
|
|
1614
|
-
updateConversation(conversationId, { model })
|
|
1615
|
-
}
|
|
1472
|
+
aiEventClient.on('embedding:started', (e) => {
|
|
1473
|
+
const { requestId, model, inputCount, timestamp, clientId } = e.payload
|
|
1474
|
+
|
|
1475
|
+
// Try to find an active conversation to attach to, or create a new one
|
|
1476
|
+
let conversationId = clientId
|
|
1477
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1478
|
+
// Find most recent active client conversation
|
|
1479
|
+
const activeClients = Object.values(state.conversations)
|
|
1480
|
+
.filter((c) => c.type === 'client' && c.status === 'active')
|
|
1481
|
+
.sort((a, b) => b.startedAt - a.startedAt)
|
|
1482
|
+
|
|
1483
|
+
if (activeClients.length > 0 && activeClients[0]) {
|
|
1484
|
+
conversationId = activeClients[0].id
|
|
1485
|
+
} else {
|
|
1486
|
+
// Create a new conversation for embeddings
|
|
1487
|
+
conversationId = `embedding-${requestId}`
|
|
1488
|
+
getOrCreateConversation(
|
|
1489
|
+
conversationId,
|
|
1490
|
+
'server',
|
|
1491
|
+
`Embedding (${model})`,
|
|
1492
|
+
)
|
|
1493
|
+
updateConversation(conversationId, { model })
|
|
1616
1494
|
}
|
|
1495
|
+
}
|
|
1617
1496
|
|
|
1618
|
-
|
|
1497
|
+
requestToConversation.set(requestId, conversationId)
|
|
1619
1498
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1499
|
+
const embeddingOp: EmbeddingOperation = {
|
|
1500
|
+
id: requestId,
|
|
1501
|
+
model,
|
|
1502
|
+
inputCount,
|
|
1503
|
+
duration: 0,
|
|
1504
|
+
timestamp,
|
|
1505
|
+
status: 'started',
|
|
1506
|
+
}
|
|
1628
1507
|
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
{ withEventTarget: false },
|
|
1640
|
-
),
|
|
1508
|
+
const conv = state.conversations[conversationId]
|
|
1509
|
+
if (conv) {
|
|
1510
|
+
const embeddings = conv.embeddings || []
|
|
1511
|
+
setState('conversations', conversationId, 'embeddings', [
|
|
1512
|
+
...embeddings,
|
|
1513
|
+
embeddingOp,
|
|
1514
|
+
])
|
|
1515
|
+
setState('conversations', conversationId, 'hasEmbedding', true)
|
|
1516
|
+
}
|
|
1517
|
+
}),
|
|
1641
1518
|
)
|
|
1642
1519
|
|
|
1643
1520
|
cleanupFns.push(
|
|
1644
|
-
aiEventClient.on(
|
|
1645
|
-
|
|
1646
|
-
(e) => {
|
|
1647
|
-
const { requestId, duration } = e.payload
|
|
1521
|
+
aiEventClient.on('embedding:completed', (e) => {
|
|
1522
|
+
const { requestId, duration } = e.payload
|
|
1648
1523
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1524
|
+
const conversationId = requestToConversation.get(requestId)
|
|
1525
|
+
if (!conversationId || !state.conversations[conversationId]) return
|
|
1651
1526
|
|
|
1652
|
-
|
|
1653
|
-
|
|
1527
|
+
const conv = state.conversations[conversationId]
|
|
1528
|
+
if (!conv.embeddings) return
|
|
1654
1529
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1530
|
+
const embeddingIndex = conv.embeddings.findIndex(
|
|
1531
|
+
(op) => op.id === requestId,
|
|
1532
|
+
)
|
|
1533
|
+
if (embeddingIndex >= 0) {
|
|
1534
|
+
setState(
|
|
1535
|
+
'conversations',
|
|
1536
|
+
conversationId,
|
|
1537
|
+
'embeddings',
|
|
1538
|
+
embeddingIndex,
|
|
1539
|
+
produce((op: EmbeddingOperation) => {
|
|
1540
|
+
op.duration = duration
|
|
1541
|
+
op.status = 'completed'
|
|
1542
|
+
}),
|
|
1657
1543
|
)
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
'conversations',
|
|
1661
|
-
conversationId,
|
|
1662
|
-
'embeddings',
|
|
1663
|
-
embeddingIndex,
|
|
1664
|
-
produce((op: EmbeddingOperation) => {
|
|
1665
|
-
op.duration = duration
|
|
1666
|
-
op.status = 'completed'
|
|
1667
|
-
}),
|
|
1668
|
-
)
|
|
1669
|
-
}
|
|
1670
|
-
},
|
|
1671
|
-
{ withEventTarget: false },
|
|
1672
|
-
),
|
|
1544
|
+
}
|
|
1545
|
+
}),
|
|
1673
1546
|
)
|
|
1674
1547
|
|
|
1675
1548
|
// ============= Summarize Events =============
|
|
1676
1549
|
|
|
1677
1550
|
cleanupFns.push(
|
|
1678
|
-
aiEventClient.on(
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
//
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
`Summarize (${model})`,
|
|
1701
|
-
)
|
|
1702
|
-
updateConversation(conversationId, { model })
|
|
1703
|
-
}
|
|
1551
|
+
aiEventClient.on('summarize:started', (e) => {
|
|
1552
|
+
const { requestId, model, inputLength, timestamp, clientId } = e.payload
|
|
1553
|
+
|
|
1554
|
+
// Try to find an active conversation to attach to, or create a new one
|
|
1555
|
+
let conversationId = clientId
|
|
1556
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1557
|
+
// Find most recent active client conversation
|
|
1558
|
+
const activeClients = Object.values(state.conversations)
|
|
1559
|
+
.filter((c) => c.type === 'client' && c.status === 'active')
|
|
1560
|
+
.sort((a, b) => b.startedAt - a.startedAt)
|
|
1561
|
+
|
|
1562
|
+
if (activeClients.length > 0 && activeClients[0]) {
|
|
1563
|
+
conversationId = activeClients[0].id
|
|
1564
|
+
} else {
|
|
1565
|
+
// Create a new conversation for summaries
|
|
1566
|
+
conversationId = `summarize-${requestId}`
|
|
1567
|
+
getOrCreateConversation(
|
|
1568
|
+
conversationId,
|
|
1569
|
+
'server',
|
|
1570
|
+
`Summarize (${model})`,
|
|
1571
|
+
)
|
|
1572
|
+
updateConversation(conversationId, { model })
|
|
1704
1573
|
}
|
|
1574
|
+
}
|
|
1705
1575
|
|
|
1706
|
-
|
|
1576
|
+
requestToConversation.set(requestId, conversationId)
|
|
1707
1577
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1578
|
+
const summarizeOp: SummarizeOperation = {
|
|
1579
|
+
id: requestId,
|
|
1580
|
+
model,
|
|
1581
|
+
inputLength,
|
|
1582
|
+
timestamp,
|
|
1583
|
+
status: 'started',
|
|
1584
|
+
}
|
|
1715
1585
|
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
{ withEventTarget: false },
|
|
1727
|
-
),
|
|
1586
|
+
const conv = state.conversations[conversationId]
|
|
1587
|
+
if (conv) {
|
|
1588
|
+
const summaries = conv.summaries || []
|
|
1589
|
+
setState('conversations', conversationId, 'summaries', [
|
|
1590
|
+
...summaries,
|
|
1591
|
+
summarizeOp,
|
|
1592
|
+
])
|
|
1593
|
+
setState('conversations', conversationId, 'hasSummarize', true)
|
|
1594
|
+
}
|
|
1595
|
+
}),
|
|
1728
1596
|
)
|
|
1729
1597
|
|
|
1730
1598
|
cleanupFns.push(
|
|
1731
|
-
aiEventClient.on(
|
|
1732
|
-
|
|
1733
|
-
(e) => {
|
|
1734
|
-
const { requestId, outputLength, duration } = e.payload
|
|
1599
|
+
aiEventClient.on('summarize:completed', (e) => {
|
|
1600
|
+
const { requestId, outputLength, duration } = e.payload
|
|
1735
1601
|
|
|
1736
|
-
|
|
1737
|
-
|
|
1602
|
+
const conversationId = requestToConversation.get(requestId)
|
|
1603
|
+
if (!conversationId || !state.conversations[conversationId]) return
|
|
1738
1604
|
|
|
1739
|
-
|
|
1740
|
-
|
|
1605
|
+
const conv = state.conversations[conversationId]
|
|
1606
|
+
if (!conv.summaries) return
|
|
1741
1607
|
|
|
1742
|
-
|
|
1743
|
-
|
|
1608
|
+
const summaryIndex = conv.summaries.findIndex(
|
|
1609
|
+
(op) => op.id === requestId,
|
|
1610
|
+
)
|
|
1611
|
+
if (summaryIndex >= 0) {
|
|
1612
|
+
setState(
|
|
1613
|
+
'conversations',
|
|
1614
|
+
conversationId,
|
|
1615
|
+
'summaries',
|
|
1616
|
+
summaryIndex,
|
|
1617
|
+
produce((op: SummarizeOperation) => {
|
|
1618
|
+
op.duration = duration
|
|
1619
|
+
op.outputLength = outputLength
|
|
1620
|
+
op.status = 'completed'
|
|
1621
|
+
}),
|
|
1744
1622
|
)
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
'conversations',
|
|
1748
|
-
conversationId,
|
|
1749
|
-
'summaries',
|
|
1750
|
-
summaryIndex,
|
|
1751
|
-
produce((op: SummarizeOperation) => {
|
|
1752
|
-
op.duration = duration
|
|
1753
|
-
op.outputLength = outputLength
|
|
1754
|
-
op.status = 'completed'
|
|
1755
|
-
}),
|
|
1756
|
-
)
|
|
1757
|
-
}
|
|
1758
|
-
},
|
|
1759
|
-
{ withEventTarget: false },
|
|
1760
|
-
),
|
|
1623
|
+
}
|
|
1624
|
+
}),
|
|
1761
1625
|
)
|
|
1762
1626
|
|
|
1763
1627
|
// Cleanup all listeners on unmount
|