vanilla-agent 1.11.0 → 1.13.0
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/index.cjs +22 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.global.js +67 -60
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +22 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +83 -53
- package/src/index.ts +2 -0
- package/src/runtime/init.ts +1 -1
- package/src/ui.ts +11 -3
- package/src/utils/code-generators.ts +1246 -0
- package/src/utils/formatting.ts +25 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanilla-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Themeable, plugable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
package/src/client.ts
CHANGED
|
@@ -876,6 +876,7 @@ export class AgentWidgetClient {
|
|
|
876
876
|
// Try to extract text from final structured content
|
|
877
877
|
const parser = streamParsers.get(assistant.id);
|
|
878
878
|
let extractedText: string | null = null;
|
|
879
|
+
let asyncPending = false;
|
|
879
880
|
|
|
880
881
|
if (parser) {
|
|
881
882
|
// First check if parser already has extracted text
|
|
@@ -890,6 +891,7 @@ export class AgentWidgetClient {
|
|
|
890
891
|
// Try parser.processChunk as last resort
|
|
891
892
|
const parsedResult = parser.processChunk(contentToProcess);
|
|
892
893
|
if (parsedResult instanceof Promise) {
|
|
894
|
+
asyncPending = true;
|
|
893
895
|
parsedResult.then((result) => {
|
|
894
896
|
// Extract text from result (could be string or object)
|
|
895
897
|
const text = typeof result === 'string' ? result : result?.text ?? null;
|
|
@@ -898,6 +900,9 @@ export class AgentWidgetClient {
|
|
|
898
900
|
if (currentAssistant && currentAssistant.id === assistant.id) {
|
|
899
901
|
currentAssistant.content = text;
|
|
900
902
|
currentAssistant.streaming = false;
|
|
903
|
+
// Clean up
|
|
904
|
+
streamParsers.delete(currentAssistant.id);
|
|
905
|
+
rawContentBuffers.delete(currentAssistant.id);
|
|
901
906
|
emitMessage(currentAssistant);
|
|
902
907
|
}
|
|
903
908
|
}
|
|
@@ -909,26 +914,29 @@ export class AgentWidgetClient {
|
|
|
909
914
|
}
|
|
910
915
|
}
|
|
911
916
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
// Clean up parser and buffer
|
|
921
|
-
const parserToClose = streamParsers.get(assistant.id);
|
|
922
|
-
if (parserToClose) {
|
|
923
|
-
const closeResult = parserToClose.close?.();
|
|
924
|
-
if (closeResult instanceof Promise) {
|
|
925
|
-
closeResult.catch(() => {});
|
|
917
|
+
// Skip sync emit if we're waiting on async parser
|
|
918
|
+
if (!asyncPending) {
|
|
919
|
+
// Set content: use extracted text if available, otherwise use raw content
|
|
920
|
+
if (extractedText !== null && extractedText.trim() !== "") {
|
|
921
|
+
assistant.content = extractedText;
|
|
922
|
+
} else if (!rawContentBuffers.has(assistant.id)) {
|
|
923
|
+
// Only use raw final content if we didn't accumulate chunks
|
|
924
|
+
assistant.content = ensureStringContent(finalContent);
|
|
926
925
|
}
|
|
927
|
-
|
|
926
|
+
|
|
927
|
+
// Clean up parser and buffer
|
|
928
|
+
const parserToClose = streamParsers.get(assistant.id);
|
|
929
|
+
if (parserToClose) {
|
|
930
|
+
const closeResult = parserToClose.close?.();
|
|
931
|
+
if (closeResult instanceof Promise) {
|
|
932
|
+
closeResult.catch(() => {});
|
|
933
|
+
}
|
|
934
|
+
streamParsers.delete(assistant.id);
|
|
935
|
+
}
|
|
936
|
+
rawContentBuffers.delete(assistant.id);
|
|
937
|
+
assistant.streaming = false;
|
|
938
|
+
emitMessage(assistant);
|
|
928
939
|
}
|
|
929
|
-
rawContentBuffers.delete(assistant.id);
|
|
930
|
-
assistant.streaming = false;
|
|
931
|
-
emitMessage(assistant);
|
|
932
940
|
}
|
|
933
941
|
}
|
|
934
942
|
} else if (payloadType === "step_complete") {
|
|
@@ -945,6 +953,7 @@ export class AgentWidgetClient {
|
|
|
945
953
|
// Check if we already have extracted text from streaming
|
|
946
954
|
const parser = streamParsers.get(assistant.id);
|
|
947
955
|
let hasExtractedText = false;
|
|
956
|
+
let asyncPending = false;
|
|
948
957
|
|
|
949
958
|
if (parser) {
|
|
950
959
|
// First check if parser already extracted text during streaming
|
|
@@ -971,6 +980,7 @@ export class AgentWidgetClient {
|
|
|
971
980
|
// Try parser
|
|
972
981
|
const parsedResult = parser.processChunk(contentToProcess);
|
|
973
982
|
if (parsedResult instanceof Promise) {
|
|
983
|
+
asyncPending = true;
|
|
974
984
|
parsedResult.then((result) => {
|
|
975
985
|
// Extract text from result (could be string or object)
|
|
976
986
|
const text = typeof result === 'string' ? result : result?.text ?? null;
|
|
@@ -980,22 +990,27 @@ export class AgentWidgetClient {
|
|
|
980
990
|
if (currentAssistant && currentAssistant.id === assistant.id) {
|
|
981
991
|
currentAssistant.content = text;
|
|
982
992
|
currentAssistant.streaming = false;
|
|
993
|
+
// Clean up
|
|
994
|
+
streamParsers.delete(currentAssistant.id);
|
|
995
|
+
rawContentBuffers.delete(currentAssistant.id);
|
|
983
996
|
emitMessage(currentAssistant);
|
|
984
997
|
}
|
|
985
998
|
} else {
|
|
986
999
|
// No extracted text - check if we should show raw content
|
|
987
1000
|
const finalExtractedText = parser.getExtractedText();
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1001
|
+
const currentAssistant = assistantMessage;
|
|
1002
|
+
if (currentAssistant && currentAssistant.id === assistant.id) {
|
|
1003
|
+
if (finalExtractedText !== null && finalExtractedText.trim() !== "") {
|
|
1004
|
+
currentAssistant.content = finalExtractedText;
|
|
1005
|
+
} else if (!rawContentBuffers.has(currentAssistant.id)) {
|
|
992
1006
|
// Only show raw content if we never had any extracted text
|
|
993
|
-
|
|
994
|
-
currentAssistant.content = ensureStringContent(finalContent);
|
|
995
|
-
}
|
|
996
|
-
currentAssistant.streaming = false;
|
|
997
|
-
emitMessage(currentAssistant);
|
|
1007
|
+
currentAssistant.content = ensureStringContent(finalContent);
|
|
998
1008
|
}
|
|
1009
|
+
currentAssistant.streaming = false;
|
|
1010
|
+
// Clean up
|
|
1011
|
+
streamParsers.delete(currentAssistant.id);
|
|
1012
|
+
rawContentBuffers.delete(currentAssistant.id);
|
|
1013
|
+
emitMessage(currentAssistant);
|
|
999
1014
|
}
|
|
1000
1015
|
}
|
|
1001
1016
|
});
|
|
@@ -1019,29 +1034,32 @@ export class AgentWidgetClient {
|
|
|
1019
1034
|
}
|
|
1020
1035
|
}
|
|
1021
1036
|
|
|
1022
|
-
//
|
|
1023
|
-
if (!
|
|
1024
|
-
|
|
1025
|
-
assistant.rawContent
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
// Only show raw content if we never extracted any text and no buffer was used
|
|
1029
|
-
if (!hasExtractedText && !rawContentBuffers.has(assistant.id)) {
|
|
1030
|
-
// No extracted text and no streaming happened - show raw content
|
|
1031
|
-
assistant.content = ensureStringContent(finalContent);
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
// Clean up parser and buffer
|
|
1035
|
-
if (parser) {
|
|
1036
|
-
const closeResult = parser.close?.();
|
|
1037
|
-
if (closeResult instanceof Promise) {
|
|
1038
|
-
closeResult.catch(() => {});
|
|
1037
|
+
// Skip sync emit if we're waiting on async parser
|
|
1038
|
+
if (!asyncPending) {
|
|
1039
|
+
// Ensure rawContent is set even if there's no parser (for action parsing)
|
|
1040
|
+
if (!assistant.rawContent) {
|
|
1041
|
+
const rawBuffer = rawContentBuffers.get(assistant.id);
|
|
1042
|
+
assistant.rawContent = rawBuffer ?? ensureStringContent(finalContent);
|
|
1039
1043
|
}
|
|
1044
|
+
|
|
1045
|
+
// Only show raw content if we never extracted any text and no buffer was used
|
|
1046
|
+
if (!hasExtractedText && !rawContentBuffers.has(assistant.id)) {
|
|
1047
|
+
// No extracted text and no streaming happened - show raw content
|
|
1048
|
+
assistant.content = ensureStringContent(finalContent);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Clean up parser and buffer
|
|
1052
|
+
if (parser) {
|
|
1053
|
+
const closeResult = parser.close?.();
|
|
1054
|
+
if (closeResult instanceof Promise) {
|
|
1055
|
+
closeResult.catch(() => {});
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
streamParsers.delete(assistant.id);
|
|
1059
|
+
rawContentBuffers.delete(assistant.id);
|
|
1060
|
+
assistant.streaming = false;
|
|
1061
|
+
emitMessage(assistant);
|
|
1040
1062
|
}
|
|
1041
|
-
streamParsers.delete(assistant.id);
|
|
1042
|
-
rawContentBuffers.delete(assistant.id);
|
|
1043
|
-
assistant.streaming = false;
|
|
1044
|
-
emitMessage(assistant);
|
|
1045
1063
|
} else {
|
|
1046
1064
|
// No final content, just mark as complete and clean up
|
|
1047
1065
|
streamParsers.delete(assistant.id);
|
|
@@ -1090,12 +1108,20 @@ export class AgentWidgetClient {
|
|
|
1090
1108
|
// Clean up parser and buffer
|
|
1091
1109
|
streamParsers.delete(assistant.id);
|
|
1092
1110
|
rawContentBuffers.delete(assistant.id);
|
|
1093
|
-
|
|
1111
|
+
|
|
1112
|
+
// Only emit if something actually changed to avoid flicker
|
|
1113
|
+
const contentChanged = displayContent !== assistant.content;
|
|
1114
|
+
const streamingChanged = assistant.streaming !== false;
|
|
1115
|
+
|
|
1116
|
+
if (contentChanged) {
|
|
1094
1117
|
assistant.content = displayContent;
|
|
1095
|
-
emitMessage(assistant);
|
|
1096
1118
|
}
|
|
1097
1119
|
assistant.streaming = false;
|
|
1098
|
-
|
|
1120
|
+
|
|
1121
|
+
// Only emit if content or streaming state changed
|
|
1122
|
+
if (contentChanged || streamingChanged) {
|
|
1123
|
+
emitMessage(assistant);
|
|
1124
|
+
}
|
|
1099
1125
|
} else {
|
|
1100
1126
|
// No final content, just mark as complete and clean up
|
|
1101
1127
|
if (assistantMessage !== null) {
|
|
@@ -1104,8 +1130,12 @@ export class AgentWidgetClient {
|
|
|
1104
1130
|
const msg: AgentWidgetMessage = assistantMessage;
|
|
1105
1131
|
streamParsers.delete(msg.id);
|
|
1106
1132
|
rawContentBuffers.delete(msg.id);
|
|
1107
|
-
|
|
1108
|
-
|
|
1133
|
+
|
|
1134
|
+
// Only emit if streaming state changed
|
|
1135
|
+
if (msg.streaming !== false) {
|
|
1136
|
+
msg.streaming = false;
|
|
1137
|
+
emitMessage(msg);
|
|
1138
|
+
}
|
|
1109
1139
|
}
|
|
1110
1140
|
}
|
|
1111
1141
|
onEvent({ type: "status", status: "idle" });
|
package/src/index.ts
CHANGED
|
@@ -59,6 +59,8 @@ export {
|
|
|
59
59
|
createRegexJsonParser,
|
|
60
60
|
createXmlParser
|
|
61
61
|
} from "./utils/formatting";
|
|
62
|
+
export { generateCodeSnippet } from "./utils/code-generators";
|
|
63
|
+
export type { CodeFormat } from "./utils/code-generators";
|
|
62
64
|
export type { AgentWidgetInitHandle };
|
|
63
65
|
|
|
64
66
|
// Plugin system exports
|
package/src/runtime/init.ts
CHANGED
package/src/ui.ts
CHANGED
|
@@ -764,8 +764,8 @@ export const createAgentExperience = (
|
|
|
764
764
|
};
|
|
765
765
|
|
|
766
766
|
|
|
767
|
-
// Message rendering with plugin support
|
|
768
|
-
const
|
|
767
|
+
// Message rendering with plugin support (implementation)
|
|
768
|
+
const renderMessagesWithPluginsImpl = (
|
|
769
769
|
container: HTMLElement,
|
|
770
770
|
messages: AgentWidgetMessage[],
|
|
771
771
|
transform: MessageTransform
|
|
@@ -915,8 +915,13 @@ export const createAgentExperience = (
|
|
|
915
915
|
const hasStreamingAssistantMessage = messages.some(
|
|
916
916
|
(msg) => msg.role === "assistant" && msg.streaming
|
|
917
917
|
);
|
|
918
|
+
|
|
919
|
+
// Also check if there's a recently completed assistant message (streaming just ended)
|
|
920
|
+
// This prevents flicker when the message completes but isStreaming hasn't updated yet
|
|
921
|
+
const lastMessage = messages[messages.length - 1];
|
|
922
|
+
const hasRecentAssistantResponse = lastMessage?.role === "assistant" && !lastMessage.streaming;
|
|
918
923
|
|
|
919
|
-
if (isStreaming && messages.some((msg) => msg.role === "user") && !hasStreamingAssistantMessage) {
|
|
924
|
+
if (isStreaming && messages.some((msg) => msg.role === "user") && !hasStreamingAssistantMessage && !hasRecentAssistantResponse) {
|
|
920
925
|
const typingIndicator = createTypingIndicator();
|
|
921
926
|
|
|
922
927
|
// Create a bubble wrapper for the typing indicator (similar to assistant messages)
|
|
@@ -956,6 +961,9 @@ export const createAgentExperience = (
|
|
|
956
961
|
});
|
|
957
962
|
};
|
|
958
963
|
|
|
964
|
+
// Alias for clarity - the implementation handles flicker prevention via typing indicator logic
|
|
965
|
+
const renderMessagesWithPlugins = renderMessagesWithPluginsImpl;
|
|
966
|
+
|
|
959
967
|
const updateOpenState = () => {
|
|
960
968
|
if (!launcherEnabled) return;
|
|
961
969
|
if (open) {
|