@assistant-ui/react-langgraph 0.5.5 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LangGraphMessageAccumulator.d.ts +0 -4
- package/dist/LangGraphMessageAccumulator.d.ts.map +1 -1
- package/dist/LangGraphMessageAccumulator.js.map +1 -1
- package/dist/appendLangChainChunk.d.ts.map +1 -1
- package/dist/appendLangChainChunk.js +23 -10
- package/dist/appendLangChainChunk.js.map +1 -1
- package/dist/types.d.ts +21 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -1
- package/dist/useLangGraphMessages.d.ts +2 -1
- package/dist/useLangGraphMessages.d.ts.map +1 -1
- package/dist/useLangGraphMessages.js +40 -7
- package/dist/useLangGraphMessages.js.map +1 -1
- package/package.json +12 -6
- package/src/LangGraphMessageAccumulator.ts +0 -11
- package/src/appendLangChainChunk.ts +31 -11
- package/src/types.ts +27 -6
- package/src/useLangGraphMessages.test.ts +626 -0
- package/src/useLangGraphMessages.ts +57 -17
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
export type LangGraphMessagesEvent<TMessage> = {
|
|
2
|
-
event: "messages" | "messages/partial" | "messages/complete" | "metadata" | "updates" | string;
|
|
3
|
-
data: TMessage[] | any;
|
|
4
|
-
};
|
|
5
1
|
export type LangGraphStateAccumulatorConfig<TMessage> = {
|
|
6
2
|
initialMessages?: TMessage[];
|
|
7
3
|
appendMessage?: (prev: TMessage | undefined, curr: TMessage) => TMessage;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LangGraphMessageAccumulator.d.ts","sourceRoot":"","sources":["../src/LangGraphMessageAccumulator.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"LangGraphMessageAccumulator.d.ts","sourceRoot":"","sources":["../src/LangGraphMessageAccumulator.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,+BAA+B,CAAC,QAAQ,IAAI;IACtD,eAAe,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC;CAC1E,CAAC;AAEF,qBAAa,2BAA2B,CAAC,QAAQ,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE;IACvE,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,aAAa,CAGP;gBAEF,EACV,eAAoB,EACpB,aAGa,GACd,GAAE,+BAA+B,CAAC,QAAQ,CAAM;IAKjD,OAAO,CAAC,eAAe;IAIhB,WAAW,CAAC,WAAW,EAAE,QAAQ,EAAE;IAenC,WAAW,IAAI,QAAQ,EAAE;IAIzB,KAAK;CAGb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/LangGraphMessageAccumulator.ts"],"sourcesContent":["import { v4 as uuidv4 } from \"uuid\";\n\nexport type
|
|
1
|
+
{"version":3,"sources":["../src/LangGraphMessageAccumulator.ts"],"sourcesContent":["import { v4 as uuidv4 } from \"uuid\";\n\nexport type LangGraphStateAccumulatorConfig<TMessage> = {\n initialMessages?: TMessage[];\n appendMessage?: (prev: TMessage | undefined, curr: TMessage) => TMessage;\n};\n\nexport class LangGraphMessageAccumulator<TMessage extends { id?: string }> {\n private messagesMap = new Map<string, TMessage>();\n private appendMessage: (\n prev: TMessage | undefined,\n curr: TMessage,\n ) => TMessage;\n\n constructor({\n initialMessages = [],\n appendMessage = ((_: TMessage | undefined, curr: TMessage) => curr) as (\n prev: TMessage | undefined,\n curr: TMessage,\n ) => TMessage,\n }: LangGraphStateAccumulatorConfig<TMessage> = {}) {\n this.appendMessage = appendMessage;\n this.addMessages(initialMessages);\n }\n\n private ensureMessageId(message: TMessage): TMessage {\n return message.id ? message : { ...message, id: uuidv4() };\n }\n\n public addMessages(newMessages: TMessage[]) {\n if (newMessages.length === 0) return this.getMessages();\n\n for (const message of newMessages.map(this.ensureMessageId)) {\n const previous = message.id\n ? this.messagesMap.get(message.id)\n : undefined;\n this.messagesMap.set(\n message.id ?? uuidv4(),\n this.appendMessage(previous, message),\n );\n }\n return this.getMessages();\n }\n\n public getMessages(): TMessage[] {\n return [...this.messagesMap.values()];\n }\n\n public clear() {\n this.messagesMap.clear();\n }\n}\n"],"mappings":";AAAA,SAAS,MAAM,cAAc;AAOtB,IAAM,8BAAN,MAAoE;AAAA,EACjE,cAAc,oBAAI,IAAsB;AAAA,EACxC;AAAA,EAKR,YAAY;AAAA,IACV,kBAAkB,CAAC;AAAA,IACnB,gBAAiB,CAAC,GAAyB,SAAmB;AAAA,EAIhE,IAA+C,CAAC,GAAG;AACjD,SAAK,gBAAgB;AACrB,SAAK,YAAY,eAAe;AAAA,EAClC;AAAA,EAEQ,gBAAgB,SAA6B;AACnD,WAAO,QAAQ,KAAK,UAAU,EAAE,GAAG,SAAS,IAAI,OAAO,EAAE;AAAA,EAC3D;AAAA,EAEO,YAAY,aAAyB;AAC1C,QAAI,YAAY,WAAW,EAAG,QAAO,KAAK,YAAY;AAEtD,eAAW,WAAW,YAAY,IAAI,KAAK,eAAe,GAAG;AAC3D,YAAM,WAAW,QAAQ,KACrB,KAAK,YAAY,IAAI,QAAQ,EAAE,IAC/B;AACJ,WAAK,YAAY;AAAA,QACf,QAAQ,MAAM,OAAO;AAAA,QACrB,KAAK,cAAc,UAAU,OAAO;AAAA,MACtC;AAAA,IACF;AACA,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA,EAEO,cAA0B;AAC/B,WAAO,CAAC,GAAG,KAAK,YAAY,OAAO,CAAC;AAAA,EACtC;AAAA,EAEO,QAAQ;AACb,SAAK,YAAY,MAAM;AAAA,EACzB;AACF;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appendLangChainChunk.d.ts","sourceRoot":"","sources":["../src/appendLangChainChunk.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"appendLangChainChunk.d.ts","sourceRoot":"","sources":["../src/appendLangChainChunk.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EAEtB,MAAM,SAAS,CAAC;AAGjB,eAAO,MAAM,oBAAoB,GAC/B,MAAM,gBAAgB,GAAG,SAAS,EAClC,MAAM,gBAAgB,GAAG,qBAAqB,KAC7C,gBAgEF,CAAC"}
|
|
@@ -11,19 +11,32 @@ var appendLangChainChunk = (prev, curr) => {
|
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
const newContent = typeof prev.content === "string" ? [{ type: "text", text: prev.content }] : [...prev.content];
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
if (typeof curr?.content === "string") {
|
|
15
|
+
const lastIndex = newContent.length - 1;
|
|
16
|
+
if (newContent[lastIndex]?.type === "text") {
|
|
17
|
+
newContent[lastIndex].text = newContent[lastIndex].text + curr.content;
|
|
18
|
+
} else {
|
|
19
|
+
newContent.push({ type: "text", text: curr.content });
|
|
20
|
+
}
|
|
21
|
+
} else if (Array.isArray(curr.content)) {
|
|
22
|
+
const lastIndex = newContent.length - 1;
|
|
23
|
+
for (const item of curr.content) {
|
|
24
|
+
if (!("type" in item)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (item.type === "text") {
|
|
28
|
+
if (newContent[lastIndex]?.type === "text") {
|
|
29
|
+
newContent[lastIndex].text = newContent[lastIndex].text + item.text;
|
|
30
|
+
} else {
|
|
31
|
+
newContent.push({ type: "text", text: item.text });
|
|
32
|
+
}
|
|
33
|
+
} else if (item.type === "image_url") {
|
|
34
|
+
newContent.push(item);
|
|
35
|
+
}
|
|
23
36
|
}
|
|
24
37
|
}
|
|
25
38
|
const newToolCalls = [...prev.tool_calls ?? []];
|
|
26
|
-
for (const chunk of curr.tool_call_chunks) {
|
|
39
|
+
for (const chunk of curr.tool_call_chunks ?? []) {
|
|
27
40
|
const existing = newToolCalls[chunk.index - 1] ?? { argsText: "" };
|
|
28
41
|
const newArgsText = existing.argsText + chunk.args;
|
|
29
42
|
newToolCalls[chunk.index - 1] = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/appendLangChainChunk.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../src/appendLangChainChunk.ts"],"sourcesContent":["import {\n LangChainMessage,\n LangChainMessageChunk,\n MessageContentText,\n} from \"./types\";\nimport { parsePartialJsonObject } from \"assistant-stream/utils\";\n\nexport const appendLangChainChunk = (\n prev: LangChainMessage | undefined,\n curr: LangChainMessage | LangChainMessageChunk,\n): LangChainMessage => {\n if (curr.type !== \"AIMessageChunk\") {\n return curr;\n }\n\n if (!prev || prev.type !== \"ai\") {\n return {\n ...curr,\n type: curr.type.replace(\"MessageChunk\", \"\").toLowerCase(),\n } as LangChainMessage;\n }\n\n const newContent =\n typeof prev.content === \"string\"\n ? [{ type: \"text\" as const, text: prev.content }]\n : [...prev.content];\n\n if (typeof curr?.content === \"string\") {\n const lastIndex = newContent.length - 1;\n if (newContent[lastIndex]?.type === \"text\") {\n (newContent[lastIndex] as MessageContentText).text =\n (newContent[lastIndex] as MessageContentText).text + curr.content;\n } else {\n newContent.push({ type: \"text\", text: curr.content });\n }\n } else if (Array.isArray(curr.content)) {\n const lastIndex = newContent.length - 1;\n for (const item of curr.content) {\n if (!(\"type\" in item)) {\n continue;\n }\n\n if (item.type === \"text\") {\n if (newContent[lastIndex]?.type === \"text\") {\n (newContent[lastIndex] as MessageContentText).text =\n (newContent[lastIndex] as MessageContentText).text + item.text;\n } else {\n newContent.push({ type: \"text\", text: item.text });\n }\n } else if (item.type === \"image_url\") {\n newContent.push(item);\n }\n }\n }\n\n const newToolCalls = [...(prev.tool_calls ?? [])];\n for (const chunk of curr.tool_call_chunks ?? []) {\n const existing = newToolCalls[chunk.index - 1] ?? { argsText: \"\" };\n const newArgsText = existing.argsText + chunk.args;\n newToolCalls[chunk.index - 1] = {\n ...chunk,\n ...existing,\n argsText: newArgsText,\n args:\n parsePartialJsonObject(newArgsText) ??\n (\"args\" in existing ? existing.args : {}),\n };\n }\n\n return {\n ...prev,\n content: newContent,\n tool_calls: newToolCalls,\n };\n};\n"],"mappings":";AAKA,SAAS,8BAA8B;AAEhC,IAAM,uBAAuB,CAClC,MACA,SACqB;AACrB,MAAI,KAAK,SAAS,kBAAkB;AAClC,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,QAAQ,KAAK,SAAS,MAAM;AAC/B,WAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM,KAAK,KAAK,QAAQ,gBAAgB,EAAE,EAAE,YAAY;AAAA,IAC1D;AAAA,EACF;AAEA,QAAM,aACJ,OAAO,KAAK,YAAY,WACpB,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,QAAQ,CAAC,IAC9C,CAAC,GAAG,KAAK,OAAO;AAEtB,MAAI,OAAO,MAAM,YAAY,UAAU;AACrC,UAAM,YAAY,WAAW,SAAS;AACtC,QAAI,WAAW,SAAS,GAAG,SAAS,QAAQ;AAC1C,MAAC,WAAW,SAAS,EAAyB,OAC3C,WAAW,SAAS,EAAyB,OAAO,KAAK;AAAA,IAC9D,OAAO;AACL,iBAAW,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,QAAQ,CAAC;AAAA,IACtD;AAAA,EACF,WAAW,MAAM,QAAQ,KAAK,OAAO,GAAG;AACtC,UAAM,YAAY,WAAW,SAAS;AACtC,eAAW,QAAQ,KAAK,SAAS;AAC/B,UAAI,EAAE,UAAU,OAAO;AACrB;AAAA,MACF;AAEA,UAAI,KAAK,SAAS,QAAQ;AACxB,YAAI,WAAW,SAAS,GAAG,SAAS,QAAQ;AAC1C,UAAC,WAAW,SAAS,EAAyB,OAC3C,WAAW,SAAS,EAAyB,OAAO,KAAK;AAAA,QAC9D,OAAO;AACL,qBAAW,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,KAAK,CAAC;AAAA,QACnD;AAAA,MACF,WAAW,KAAK,SAAS,aAAa;AACpC,mBAAW,KAAK,IAAI;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,CAAC,GAAI,KAAK,cAAc,CAAC,CAAE;AAChD,aAAW,SAAS,KAAK,oBAAoB,CAAC,GAAG;AAC/C,UAAM,WAAW,aAAa,MAAM,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG;AACjE,UAAM,cAAc,SAAS,WAAW,MAAM;AAC9C,iBAAa,MAAM,QAAQ,CAAC,IAAI;AAAA,MAC9B,GAAG;AAAA,MACH,GAAG;AAAA,MACH,UAAU;AAAA,MACV,MACE,uBAAuB,WAAW,MACjC,UAAU,WAAW,SAAS,OAAO,CAAC;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,IACT,YAAY;AAAA,EACd;AACF;","names":[]}
|
package/dist/types.d.ts
CHANGED
|
@@ -11,11 +11,11 @@ export type LangChainToolCall = {
|
|
|
11
11
|
argsText: string;
|
|
12
12
|
args: ReadonlyJSONObject;
|
|
13
13
|
};
|
|
14
|
-
type MessageContentText = {
|
|
14
|
+
export type MessageContentText = {
|
|
15
15
|
type: "text";
|
|
16
16
|
text: string;
|
|
17
17
|
};
|
|
18
|
-
type MessageContentImageUrl = {
|
|
18
|
+
export type MessageContentImageUrl = {
|
|
19
19
|
type: "image_url";
|
|
20
20
|
image_url: string | {
|
|
21
21
|
url: string;
|
|
@@ -24,8 +24,17 @@ type MessageContentImageUrl = {
|
|
|
24
24
|
type MessageContentToolUse = {
|
|
25
25
|
type: "tool_use";
|
|
26
26
|
};
|
|
27
|
+
export declare enum LangGraphKnownEventTypes {
|
|
28
|
+
Messages = "messages",
|
|
29
|
+
MessagesPartial = "messages/partial",
|
|
30
|
+
MessagesComplete = "messages/complete",
|
|
31
|
+
Metadata = "metadata",
|
|
32
|
+
Updates = "updates"
|
|
33
|
+
}
|
|
34
|
+
type CustomEventType = string;
|
|
35
|
+
export type EventType = LangGraphKnownEventTypes | CustomEventType;
|
|
27
36
|
type UserMessageContentComplex = MessageContentText | MessageContentImageUrl;
|
|
28
|
-
type AssistantMessageContentComplex = MessageContentText | MessageContentToolUse;
|
|
37
|
+
type AssistantMessageContentComplex = MessageContentText | MessageContentImageUrl | MessageContentToolUse;
|
|
29
38
|
type UserMessageContent = string | UserMessageContentComplex[];
|
|
30
39
|
type AssistantMessageContent = string | AssistantMessageContentComplex[];
|
|
31
40
|
export type LangChainMessage = {
|
|
@@ -52,16 +61,19 @@ export type LangChainMessage = {
|
|
|
52
61
|
tool_calls?: LangChainToolCall[];
|
|
53
62
|
};
|
|
54
63
|
export type LangChainMessageChunk = {
|
|
55
|
-
id
|
|
64
|
+
id?: string | undefined;
|
|
56
65
|
type: "AIMessageChunk";
|
|
57
|
-
content
|
|
58
|
-
|
|
59
|
-
})[];
|
|
60
|
-
tool_call_chunks: LangChainToolCallChunk[];
|
|
66
|
+
content?: AssistantMessageContent | undefined;
|
|
67
|
+
tool_call_chunks?: LangChainToolCallChunk[] | undefined;
|
|
61
68
|
};
|
|
62
69
|
export type LangChainEvent = {
|
|
63
|
-
event:
|
|
70
|
+
event: LangGraphKnownEventTypes.MessagesPartial | LangGraphKnownEventTypes.MessagesComplete;
|
|
64
71
|
data: LangChainMessage[];
|
|
65
72
|
};
|
|
73
|
+
type LangGraphTupleMetadata = Record<string, unknown>;
|
|
74
|
+
export type LangChainMessageTupleEvent = {
|
|
75
|
+
event: LangGraphKnownEventTypes.Messages;
|
|
76
|
+
data: [LangChainMessageChunk, LangGraphTupleMetadata];
|
|
77
|
+
};
|
|
66
78
|
export {};
|
|
67
79
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,kBAAkB,CAAC;CAC1B,CAAC;AAEF,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,kBAAkB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CACrC,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,oBAAY,wBAAwB;IAClC,QAAQ,aAAa;IACrB,eAAe,qBAAqB;IACpC,gBAAgB,sBAAsB;IACtC,QAAQ,aAAa;IACrB,OAAO,YAAY;CACpB;AACD,KAAK,eAAe,GAAG,MAAM,CAAC;AAE9B,MAAM,MAAM,SAAS,GAAG,wBAAwB,GAAG,eAAe,CAAC;AAEnE,KAAK,yBAAyB,GAAG,kBAAkB,GAAG,sBAAsB,CAAC;AAC7E,KAAK,8BAA8B,GAC/B,kBAAkB,GAClB,sBAAsB,GACtB,qBAAqB,CAAC;AAE1B,KAAK,kBAAkB,GAAG,MAAM,GAAG,yBAAyB,EAAE,CAAC;AAC/D,KAAK,uBAAuB,GAAG,MAAM,GAAG,8BAA8B,EAAE,CAAC;AAEzE,MAAM,MAAM,gBAAgB,GACxB;IACE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,kBAAkB,CAAC;CAC7B,GACD;IACE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;CAC7B,GACD;IACE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,uBAAuB,CAAC;IACjC,gBAAgB,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAC5C,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAClC,CAAC;AAEN,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,CAAC,EAAE,uBAAuB,GAAG,SAAS,CAAC;IAC9C,gBAAgB,CAAC,EAAE,sBAAsB,EAAE,GAAG,SAAS,CAAC;CACzD,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EACD,wBAAwB,CAAC,eAAe,GACxC,wBAAwB,CAAC,gBAAgB,CAAC;IAC9C,IAAI,EAAE,gBAAgB,EAAE,CAAC;CAC1B,CAAC;AAEF,KAAK,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtD,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,wBAAwB,CAAC,QAAQ,CAAC;IACzC,IAAI,EAAE,CAAC,qBAAqB,EAAE,sBAAsB,CAAC,CAAC;CACvD,CAAC"}
|
package/dist/types.js
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var LangGraphKnownEventTypes = /* @__PURE__ */ ((LangGraphKnownEventTypes2) => {
|
|
3
|
+
LangGraphKnownEventTypes2["Messages"] = "messages";
|
|
4
|
+
LangGraphKnownEventTypes2["MessagesPartial"] = "messages/partial";
|
|
5
|
+
LangGraphKnownEventTypes2["MessagesComplete"] = "messages/complete";
|
|
6
|
+
LangGraphKnownEventTypes2["Metadata"] = "metadata";
|
|
7
|
+
LangGraphKnownEventTypes2["Updates"] = "updates";
|
|
8
|
+
return LangGraphKnownEventTypes2;
|
|
9
|
+
})(LangGraphKnownEventTypes || {});
|
|
10
|
+
export {
|
|
11
|
+
LangGraphKnownEventTypes
|
|
12
|
+
};
|
|
1
13
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import { ReadonlyJSONObject } from \"assistant-stream/utils\";\n\nexport type LangChainToolCallChunk = {\n index: number;\n id: string;\n name: string;\n args: string;\n};\n\nexport type LangChainToolCall = {\n id: string;\n name: string;\n argsText: string;\n args: ReadonlyJSONObject;\n};\n\nexport type MessageContentText = {\n type: \"text\";\n text: string;\n};\n\nexport type MessageContentImageUrl = {\n type: \"image_url\";\n image_url: string | { url: string };\n};\n\ntype MessageContentToolUse = {\n type: \"tool_use\";\n};\n\nexport enum LangGraphKnownEventTypes {\n Messages = \"messages\",\n MessagesPartial = \"messages/partial\",\n MessagesComplete = \"messages/complete\",\n Metadata = \"metadata\",\n Updates = \"updates\",\n}\ntype CustomEventType = string;\n\nexport type EventType = LangGraphKnownEventTypes | CustomEventType;\n\ntype UserMessageContentComplex = MessageContentText | MessageContentImageUrl;\ntype AssistantMessageContentComplex =\n | MessageContentText\n | MessageContentImageUrl\n | MessageContentToolUse;\n\ntype UserMessageContent = string | UserMessageContentComplex[];\ntype AssistantMessageContent = string | AssistantMessageContentComplex[];\n\nexport type LangChainMessage =\n | {\n id?: string;\n type: \"system\";\n content: string;\n }\n | {\n id?: string;\n type: \"human\";\n content: UserMessageContent;\n }\n | {\n id?: string;\n type: \"tool\";\n content: string;\n tool_call_id: string;\n name: string;\n artifact?: any;\n status: \"success\" | \"error\";\n }\n | {\n id?: string;\n type: \"ai\";\n content: AssistantMessageContent;\n tool_call_chunks?: LangChainToolCallChunk[];\n tool_calls?: LangChainToolCall[];\n };\n\nexport type LangChainMessageChunk = {\n id?: string | undefined;\n type: \"AIMessageChunk\";\n content?: AssistantMessageContent | undefined;\n tool_call_chunks?: LangChainToolCallChunk[] | undefined;\n};\n\nexport type LangChainEvent = {\n event:\n | LangGraphKnownEventTypes.MessagesPartial\n | LangGraphKnownEventTypes.MessagesComplete;\n data: LangChainMessage[];\n};\n\ntype LangGraphTupleMetadata = Record<string, unknown>;\n\nexport type LangChainMessageTupleEvent = {\n event: LangGraphKnownEventTypes.Messages;\n data: [LangChainMessageChunk, LangGraphTupleMetadata];\n};\n"],"mappings":";AA8BO,IAAK,2BAAL,kBAAKA,8BAAL;AACL,EAAAA,0BAAA,cAAW;AACX,EAAAA,0BAAA,qBAAkB;AAClB,EAAAA,0BAAA,sBAAmB;AACnB,EAAAA,0BAAA,cAAW;AACX,EAAAA,0BAAA,aAAU;AALA,SAAAA;AAAA,GAAA;","names":["LangGraphKnownEventTypes"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventType } from "./types";
|
|
1
2
|
export type LangGraphCommand = {
|
|
2
3
|
resume: string;
|
|
3
4
|
};
|
|
@@ -6,7 +7,7 @@ export type LangGraphSendMessageConfig = {
|
|
|
6
7
|
runConfig?: unknown;
|
|
7
8
|
};
|
|
8
9
|
export type LangGraphMessagesEvent<TMessage> = {
|
|
9
|
-
event:
|
|
10
|
+
event: EventType;
|
|
10
11
|
data: TMessage[] | any;
|
|
11
12
|
};
|
|
12
13
|
export type LangGraphStreamCallback<TMessage> = (messages: TMessage[], config: LangGraphSendMessageConfig & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLangGraphMessages.d.ts","sourceRoot":"","sources":["../src/useLangGraphMessages.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useLangGraphMessages.d.ts","sourceRoot":"","sources":["../src/useLangGraphMessages.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,SAAS,EAIV,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,sBAAsB,CAAC,QAAQ,IAAI;IAC7C,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,QAAQ,IAAI,CAC9C,QAAQ,EAAE,QAAQ,EAAE,EACpB,MAAM,EAAE,0BAA0B,GAAG;IAAE,WAAW,EAAE,WAAW,CAAA;CAAE,KAE/D,OAAO,CAAC,cAAc,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAC,GACzD,cAAc,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAC;AAErD,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;CACf,CAAC;AAuBF,eAAO,MAAM,oBAAoB,GAAI,QAAQ,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,4BAGpE;IACD,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAC1C,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC;CAC1E;;;+BAQuB,QAAQ,EAAE,UAAU,0BAA0B;;;;CAoErE,CAAC"}
|
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
import { useState, useCallback, useRef } from "react";
|
|
3
3
|
import { v4 as uuidv4 } from "uuid";
|
|
4
4
|
import { LangGraphMessageAccumulator } from "./LangGraphMessageAccumulator.js";
|
|
5
|
+
import {
|
|
6
|
+
LangGraphKnownEventTypes
|
|
7
|
+
} from "./types.js";
|
|
5
8
|
var DEFAULT_APPEND_MESSAGE = (_, curr) => curr;
|
|
9
|
+
var isLangChainMessageChunk = (value) => {
|
|
10
|
+
if (!value || typeof value !== "object") return false;
|
|
11
|
+
const chunk = value;
|
|
12
|
+
return "type" in chunk && chunk.type === "AIMessageChunk" && (chunk.content === void 0 || typeof chunk.content === "string" || Array.isArray(chunk.content)) && (chunk.tool_call_chunks === void 0 || Array.isArray(chunk.tool_call_chunks));
|
|
13
|
+
};
|
|
6
14
|
var useLangGraphMessages = ({
|
|
7
15
|
stream,
|
|
8
16
|
appendMessage = DEFAULT_APPEND_MESSAGE
|
|
@@ -12,23 +20,48 @@ var useLangGraphMessages = ({
|
|
|
12
20
|
const abortControllerRef = useRef(null);
|
|
13
21
|
const sendMessage = useCallback(
|
|
14
22
|
async (newMessages, config) => {
|
|
15
|
-
|
|
23
|
+
const newMessagesWithId = newMessages.map(
|
|
24
|
+
(m) => m.id ? m : { ...m, id: uuidv4() }
|
|
25
|
+
);
|
|
16
26
|
const accumulator = new LangGraphMessageAccumulator({
|
|
17
27
|
initialMessages: messages,
|
|
18
28
|
appendMessage
|
|
19
29
|
});
|
|
20
|
-
setMessages(accumulator.addMessages(
|
|
30
|
+
setMessages(accumulator.addMessages(newMessagesWithId));
|
|
21
31
|
const abortController = new AbortController();
|
|
22
32
|
abortControllerRef.current = abortController;
|
|
23
|
-
const response = await stream(
|
|
33
|
+
const response = await stream(newMessagesWithId, {
|
|
24
34
|
...config,
|
|
25
35
|
abortSignal: abortController.signal
|
|
26
36
|
});
|
|
27
37
|
for await (const chunk of response) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
switch (chunk.event) {
|
|
39
|
+
case LangGraphKnownEventTypes.MessagesPartial:
|
|
40
|
+
case LangGraphKnownEventTypes.MessagesComplete:
|
|
41
|
+
setMessages(accumulator.addMessages(chunk.data));
|
|
42
|
+
break;
|
|
43
|
+
case LangGraphKnownEventTypes.Updates:
|
|
44
|
+
setInterrupt(chunk.data.__interrupt__?.[0]);
|
|
45
|
+
break;
|
|
46
|
+
case LangGraphKnownEventTypes.Messages: {
|
|
47
|
+
const [messageChunk] = chunk.data;
|
|
48
|
+
if (!isLangChainMessageChunk(messageChunk)) {
|
|
49
|
+
console.warn(
|
|
50
|
+
"Received invalid message chunk format:",
|
|
51
|
+
messageChunk
|
|
52
|
+
);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
const updatedMessages = accumulator.addMessages([
|
|
56
|
+
messageChunk
|
|
57
|
+
]);
|
|
58
|
+
setMessages(updatedMessages);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case LangGraphKnownEventTypes.Metadata:
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
console.warn(`The event type ${chunk.event} is not supported.`);
|
|
32
65
|
}
|
|
33
66
|
}
|
|
34
67
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useLangGraphMessages.ts"],"sourcesContent":["import { useState, useCallback, useRef } from \"react\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { LangGraphMessageAccumulator } from \"./LangGraphMessageAccumulator\";\n\nexport type LangGraphCommand = {\n resume: string;\n};\n\nexport type LangGraphSendMessageConfig = {\n command?: LangGraphCommand;\n runConfig?: unknown;\n};\n\nexport type LangGraphMessagesEvent<TMessage> = {\n event
|
|
1
|
+
{"version":3,"sources":["../src/useLangGraphMessages.ts"],"sourcesContent":["import { useState, useCallback, useRef } from \"react\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { LangGraphMessageAccumulator } from \"./LangGraphMessageAccumulator\";\nimport {\n EventType,\n LangChainMessageTupleEvent,\n LangGraphKnownEventTypes,\n LangChainMessageChunk,\n} from \"./types\";\n\nexport type LangGraphCommand = {\n resume: string;\n};\n\nexport type LangGraphSendMessageConfig = {\n command?: LangGraphCommand;\n runConfig?: unknown;\n};\n\nexport type LangGraphMessagesEvent<TMessage> = {\n event: EventType;\n data: TMessage[] | any;\n};\n\nexport type LangGraphStreamCallback<TMessage> = (\n messages: TMessage[],\n config: LangGraphSendMessageConfig & { abortSignal: AbortSignal },\n) =>\n | Promise<AsyncGenerator<LangGraphMessagesEvent<TMessage>>>\n | AsyncGenerator<LangGraphMessagesEvent<TMessage>>;\n\nexport type LangGraphInterruptState = {\n value?: any;\n resumable?: boolean;\n when: string;\n ns?: string[];\n};\n\nconst DEFAULT_APPEND_MESSAGE = <TMessage>(\n _: TMessage | undefined,\n curr: TMessage,\n) => curr;\n\nconst isLangChainMessageChunk = (\n value: unknown,\n): value is LangChainMessageChunk => {\n if (!value || typeof value !== \"object\") return false;\n const chunk = value as any;\n return (\n \"type\" in chunk &&\n chunk.type === \"AIMessageChunk\" &&\n (chunk.content === undefined ||\n typeof chunk.content === \"string\" ||\n Array.isArray(chunk.content)) &&\n (chunk.tool_call_chunks === undefined ||\n Array.isArray(chunk.tool_call_chunks))\n );\n};\n\nexport const useLangGraphMessages = <TMessage extends { id?: string }>({\n stream,\n appendMessage = DEFAULT_APPEND_MESSAGE,\n}: {\n stream: LangGraphStreamCallback<TMessage>;\n appendMessage?: (prev: TMessage | undefined, curr: TMessage) => TMessage;\n}) => {\n const [interrupt, setInterrupt] = useState<\n LangGraphInterruptState | undefined\n >();\n const [messages, setMessages] = useState<TMessage[]>([]);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const sendMessage = useCallback(\n async (newMessages: TMessage[], config: LangGraphSendMessageConfig) => {\n // ensure all messages have an ID\n const newMessagesWithId = newMessages.map((m) =>\n m.id ? m : { ...m, id: uuidv4() },\n );\n\n const accumulator = new LangGraphMessageAccumulator({\n initialMessages: messages,\n appendMessage,\n });\n setMessages(accumulator.addMessages(newMessagesWithId));\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n const response = await stream(newMessagesWithId, {\n ...config,\n abortSignal: abortController.signal,\n });\n\n for await (const chunk of response) {\n switch (chunk.event) {\n case LangGraphKnownEventTypes.MessagesPartial:\n case LangGraphKnownEventTypes.MessagesComplete:\n setMessages(accumulator.addMessages(chunk.data));\n break;\n case LangGraphKnownEventTypes.Updates:\n setInterrupt(chunk.data.__interrupt__?.[0]);\n break;\n case LangGraphKnownEventTypes.Messages: {\n const [messageChunk] = (chunk as LangChainMessageTupleEvent).data;\n if (!isLangChainMessageChunk(messageChunk)) {\n console.warn(\n \"Received invalid message chunk format:\",\n messageChunk,\n );\n break;\n }\n const updatedMessages = accumulator.addMessages([\n messageChunk as unknown as TMessage,\n ]);\n setMessages(updatedMessages);\n break;\n }\n case LangGraphKnownEventTypes.Metadata:\n // currently this is a no-op\n break;\n default:\n console.warn(`The event type ${chunk.event} is not supported.`);\n }\n }\n },\n [messages, stream, appendMessage],\n );\n\n const cancel = useCallback(() => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n }, [abortControllerRef]);\n\n return {\n interrupt,\n messages,\n sendMessage,\n cancel,\n setInterrupt,\n setMessages,\n };\n};\n"],"mappings":";AAAA,SAAS,UAAU,aAAa,cAAc;AAC9C,SAAS,MAAM,cAAc;AAC7B,SAAS,mCAAmC;AAC5C;AAAA,EAGE;AAAA,OAEK;AA8BP,IAAM,yBAAyB,CAC7B,GACA,SACG;AAEL,IAAM,0BAA0B,CAC9B,UACmC;AACnC,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,QAAQ;AACd,SACE,UAAU,SACV,MAAM,SAAS,qBACd,MAAM,YAAY,UACjB,OAAO,MAAM,YAAY,YACzB,MAAM,QAAQ,MAAM,OAAO,OAC5B,MAAM,qBAAqB,UAC1B,MAAM,QAAQ,MAAM,gBAAgB;AAE1C;AAEO,IAAM,uBAAuB,CAAmC;AAAA,EACrE;AAAA,EACA,gBAAgB;AAClB,MAGM;AACJ,QAAM,CAAC,WAAW,YAAY,IAAI,SAEhC;AACF,QAAM,CAAC,UAAU,WAAW,IAAI,SAAqB,CAAC,CAAC;AACvD,QAAM,qBAAqB,OAA+B,IAAI;AAE9D,QAAM,cAAc;AAAA,IAClB,OAAO,aAAyB,WAAuC;AAErE,YAAM,oBAAoB,YAAY;AAAA,QAAI,CAAC,MACzC,EAAE,KAAK,IAAI,EAAE,GAAG,GAAG,IAAI,OAAO,EAAE;AAAA,MAClC;AAEA,YAAM,cAAc,IAAI,4BAA4B;AAAA,QAClD,iBAAiB;AAAA,QACjB;AAAA,MACF,CAAC;AACD,kBAAY,YAAY,YAAY,iBAAiB,CAAC;AAEtD,YAAM,kBAAkB,IAAI,gBAAgB;AAC5C,yBAAmB,UAAU;AAC7B,YAAM,WAAW,MAAM,OAAO,mBAAmB;AAAA,QAC/C,GAAG;AAAA,QACH,aAAa,gBAAgB;AAAA,MAC/B,CAAC;AAED,uBAAiB,SAAS,UAAU;AAClC,gBAAQ,MAAM,OAAO;AAAA,UACnB,KAAK,yBAAyB;AAAA,UAC9B,KAAK,yBAAyB;AAC5B,wBAAY,YAAY,YAAY,MAAM,IAAI,CAAC;AAC/C;AAAA,UACF,KAAK,yBAAyB;AAC5B,yBAAa,MAAM,KAAK,gBAAgB,CAAC,CAAC;AAC1C;AAAA,UACF,KAAK,yBAAyB,UAAU;AACtC,kBAAM,CAAC,YAAY,IAAK,MAAqC;AAC7D,gBAAI,CAAC,wBAAwB,YAAY,GAAG;AAC1C,sBAAQ;AAAA,gBACN;AAAA,gBACA;AAAA,cACF;AACA;AAAA,YACF;AACA,kBAAM,kBAAkB,YAAY,YAAY;AAAA,cAC9C;AAAA,YACF,CAAC;AACD,wBAAY,eAAe;AAC3B;AAAA,UACF;AAAA,UACA,KAAK,yBAAyB;AAE5B;AAAA,UACF;AACE,oBAAQ,KAAK,kBAAkB,MAAM,KAAK,oBAAoB;AAAA,QAClE;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,UAAU,QAAQ,aAAa;AAAA,EAClC;AAEA,QAAM,SAAS,YAAY,MAAM;AAC/B,QAAI,mBAAmB,SAAS;AAC9B,yBAAmB,QAAQ,MAAM;AAAA,IACnC;AAAA,EACF,GAAG,CAAC,kBAAkB,CAAC;AAEvB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-langgraph",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"zod": "^3.25.28"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@assistant-ui/react": "^0.10.
|
|
26
|
+
"@assistant-ui/react": "^0.10.20",
|
|
27
27
|
"@types/react": "*",
|
|
28
28
|
"react": "^18 || ^19 || ^19.0.0-rc"
|
|
29
29
|
},
|
|
@@ -33,14 +33,18 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
36
|
+
"@testing-library/dom": "^10.4.0",
|
|
37
|
+
"@testing-library/react": "^16.3.0",
|
|
38
|
+
"@types/node": "^22.15.18",
|
|
39
|
+
"@types/react": "^19.1.4",
|
|
38
40
|
"@types/uuid": "^10.0.0",
|
|
39
41
|
"eslint": "^9",
|
|
40
42
|
"eslint-config-next": "15.3.2",
|
|
43
|
+
"jsdom": "^26.1.0",
|
|
41
44
|
"react": "^19.1.0",
|
|
42
45
|
"tsx": "^4.19.4",
|
|
43
|
-
"
|
|
46
|
+
"vitest": "^3.1.3",
|
|
47
|
+
"@assistant-ui/react": "0.10.20",
|
|
44
48
|
"@assistant-ui/x-buildutils": "0.0.1"
|
|
45
49
|
},
|
|
46
50
|
"publishConfig": {
|
|
@@ -57,6 +61,8 @@
|
|
|
57
61
|
},
|
|
58
62
|
"scripts": {
|
|
59
63
|
"build": "tsx scripts/build.mts",
|
|
60
|
-
"lint": "eslint ."
|
|
64
|
+
"lint": "eslint .",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest"
|
|
61
67
|
}
|
|
62
68
|
}
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from "uuid";
|
|
2
2
|
|
|
3
|
-
export type LangGraphMessagesEvent<TMessage> = {
|
|
4
|
-
event:
|
|
5
|
-
| "messages"
|
|
6
|
-
| "messages/partial"
|
|
7
|
-
| "messages/complete"
|
|
8
|
-
| "metadata"
|
|
9
|
-
| "updates"
|
|
10
|
-
| string;
|
|
11
|
-
data: TMessage[] | any;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
3
|
export type LangGraphStateAccumulatorConfig<TMessage> = {
|
|
15
4
|
initialMessages?: TMessage[];
|
|
16
5
|
appendMessage?: (prev: TMessage | undefined, curr: TMessage) => TMessage;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
LangChainMessage,
|
|
3
|
+
LangChainMessageChunk,
|
|
4
|
+
MessageContentText,
|
|
5
|
+
} from "./types";
|
|
2
6
|
import { parsePartialJsonObject } from "assistant-stream/utils";
|
|
3
7
|
|
|
4
8
|
export const appendLangChainChunk = (
|
|
@@ -21,20 +25,36 @@ export const appendLangChainChunk = (
|
|
|
21
25
|
? [{ type: "text" as const, text: prev.content }]
|
|
22
26
|
: [...prev.content];
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
if (typeof curr?.content === "string") {
|
|
29
|
+
const lastIndex = newContent.length - 1;
|
|
30
|
+
if (newContent[lastIndex]?.type === "text") {
|
|
31
|
+
(newContent[lastIndex] as MessageContentText).text =
|
|
32
|
+
(newContent[lastIndex] as MessageContentText).text + curr.content;
|
|
33
|
+
} else {
|
|
34
|
+
newContent.push({ type: "text", text: curr.content });
|
|
35
|
+
}
|
|
36
|
+
} else if (Array.isArray(curr.content)) {
|
|
37
|
+
const lastIndex = newContent.length - 1;
|
|
38
|
+
for (const item of curr.content) {
|
|
39
|
+
if (!("type" in item)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (item.type === "text") {
|
|
44
|
+
if (newContent[lastIndex]?.type === "text") {
|
|
45
|
+
(newContent[lastIndex] as MessageContentText).text =
|
|
46
|
+
(newContent[lastIndex] as MessageContentText).text + item.text;
|
|
47
|
+
} else {
|
|
48
|
+
newContent.push({ type: "text", text: item.text });
|
|
49
|
+
}
|
|
50
|
+
} else if (item.type === "image_url") {
|
|
51
|
+
newContent.push(item);
|
|
52
|
+
}
|
|
33
53
|
}
|
|
34
54
|
}
|
|
35
55
|
|
|
36
56
|
const newToolCalls = [...(prev.tool_calls ?? [])];
|
|
37
|
-
for (const chunk of curr.tool_call_chunks) {
|
|
57
|
+
for (const chunk of curr.tool_call_chunks ?? []) {
|
|
38
58
|
const existing = newToolCalls[chunk.index - 1] ?? { argsText: "" };
|
|
39
59
|
const newArgsText = existing.argsText + chunk.args;
|
|
40
60
|
newToolCalls[chunk.index - 1] = {
|
package/src/types.ts
CHANGED
|
@@ -14,12 +14,12 @@ export type LangChainToolCall = {
|
|
|
14
14
|
args: ReadonlyJSONObject;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
type MessageContentText = {
|
|
17
|
+
export type MessageContentText = {
|
|
18
18
|
type: "text";
|
|
19
19
|
text: string;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
type MessageContentImageUrl = {
|
|
22
|
+
export type MessageContentImageUrl = {
|
|
23
23
|
type: "image_url";
|
|
24
24
|
image_url: string | { url: string };
|
|
25
25
|
};
|
|
@@ -28,9 +28,21 @@ type MessageContentToolUse = {
|
|
|
28
28
|
type: "tool_use";
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
export enum LangGraphKnownEventTypes {
|
|
32
|
+
Messages = "messages",
|
|
33
|
+
MessagesPartial = "messages/partial",
|
|
34
|
+
MessagesComplete = "messages/complete",
|
|
35
|
+
Metadata = "metadata",
|
|
36
|
+
Updates = "updates",
|
|
37
|
+
}
|
|
38
|
+
type CustomEventType = string;
|
|
39
|
+
|
|
40
|
+
export type EventType = LangGraphKnownEventTypes | CustomEventType;
|
|
41
|
+
|
|
31
42
|
type UserMessageContentComplex = MessageContentText | MessageContentImageUrl;
|
|
32
43
|
type AssistantMessageContentComplex =
|
|
33
44
|
| MessageContentText
|
|
45
|
+
| MessageContentImageUrl
|
|
34
46
|
| MessageContentToolUse;
|
|
35
47
|
|
|
36
48
|
type UserMessageContent = string | UserMessageContentComplex[];
|
|
@@ -65,13 +77,22 @@ export type LangChainMessage =
|
|
|
65
77
|
};
|
|
66
78
|
|
|
67
79
|
export type LangChainMessageChunk = {
|
|
68
|
-
id
|
|
80
|
+
id?: string | undefined;
|
|
69
81
|
type: "AIMessageChunk";
|
|
70
|
-
content
|
|
71
|
-
tool_call_chunks
|
|
82
|
+
content?: AssistantMessageContent | undefined;
|
|
83
|
+
tool_call_chunks?: LangChainToolCallChunk[] | undefined;
|
|
72
84
|
};
|
|
73
85
|
|
|
74
86
|
export type LangChainEvent = {
|
|
75
|
-
event:
|
|
87
|
+
event:
|
|
88
|
+
| LangGraphKnownEventTypes.MessagesPartial
|
|
89
|
+
| LangGraphKnownEventTypes.MessagesComplete;
|
|
76
90
|
data: LangChainMessage[];
|
|
77
91
|
};
|
|
92
|
+
|
|
93
|
+
type LangGraphTupleMetadata = Record<string, unknown>;
|
|
94
|
+
|
|
95
|
+
export type LangChainMessageTupleEvent = {
|
|
96
|
+
event: LangGraphKnownEventTypes.Messages;
|
|
97
|
+
data: [LangChainMessageChunk, LangGraphTupleMetadata];
|
|
98
|
+
};
|
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
LangGraphMessagesEvent,
|
|
6
|
+
useLangGraphMessages,
|
|
7
|
+
} from "./useLangGraphMessages";
|
|
8
|
+
import { appendLangChainChunk } from "./appendLangChainChunk";
|
|
9
|
+
import {
|
|
10
|
+
LangChainMessage,
|
|
11
|
+
MessageContentImageUrl,
|
|
12
|
+
MessageContentText,
|
|
13
|
+
} from "./types";
|
|
14
|
+
|
|
15
|
+
const metadataEvent = {
|
|
16
|
+
event: "metadata",
|
|
17
|
+
data: {
|
|
18
|
+
thread_id: "123",
|
|
19
|
+
run_attempt: 1,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const mockStreamCallbackFactory = (
|
|
24
|
+
events: Array<LangGraphMessagesEvent<LangChainMessage>>,
|
|
25
|
+
) =>
|
|
26
|
+
async function* () {
|
|
27
|
+
for (const event of events) {
|
|
28
|
+
yield event;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe("useLangGraphMessages", {}, () => {
|
|
33
|
+
it("processes chunks correctly", async () => {
|
|
34
|
+
const mockStreamCallback = mockStreamCallbackFactory([
|
|
35
|
+
metadataEvent,
|
|
36
|
+
{
|
|
37
|
+
event: "messages",
|
|
38
|
+
data: [
|
|
39
|
+
{
|
|
40
|
+
id: "run-1",
|
|
41
|
+
content: "",
|
|
42
|
+
additional_kwargs: {},
|
|
43
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
44
|
+
type: "AIMessageChunk",
|
|
45
|
+
name: null,
|
|
46
|
+
tool_calls: [],
|
|
47
|
+
invalid_tool_calls: [],
|
|
48
|
+
tool_call_chunks: [],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
run_attempt: 1,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const { result } = renderHook(() =>
|
|
58
|
+
useLangGraphMessages({
|
|
59
|
+
stream: mockStreamCallback,
|
|
60
|
+
appendMessage: appendLangChainChunk,
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
act(() => {
|
|
65
|
+
result.current.sendMessage(
|
|
66
|
+
[
|
|
67
|
+
{
|
|
68
|
+
type: "human",
|
|
69
|
+
content: "Hello, world!",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
{},
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await waitFor(() => {
|
|
77
|
+
expect(result.current.messages.length).toEqual(2);
|
|
78
|
+
expect(result.current.messages[0].type).toEqual("human");
|
|
79
|
+
expect(result.current.messages[1].type).toEqual("ai");
|
|
80
|
+
expect(result.current.messages[1].content).toEqual("");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("appends chunks w/ same id", async () => {
|
|
85
|
+
const mockStreamCallback = mockStreamCallbackFactory([
|
|
86
|
+
metadataEvent,
|
|
87
|
+
{
|
|
88
|
+
event: "messages",
|
|
89
|
+
data: [
|
|
90
|
+
{
|
|
91
|
+
id: "run-1",
|
|
92
|
+
content: "",
|
|
93
|
+
additional_kwargs: {},
|
|
94
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
95
|
+
type: "AIMessageChunk",
|
|
96
|
+
name: null,
|
|
97
|
+
tool_calls: [],
|
|
98
|
+
invalid_tool_calls: [],
|
|
99
|
+
tool_call_chunks: [],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
run_attempt: 1,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
event: "messages",
|
|
108
|
+
data: [
|
|
109
|
+
{
|
|
110
|
+
id: "run-1",
|
|
111
|
+
content: "Hello!",
|
|
112
|
+
additional_kwargs: {},
|
|
113
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
114
|
+
type: "AIMessageChunk",
|
|
115
|
+
name: null,
|
|
116
|
+
tool_calls: [],
|
|
117
|
+
invalid_tool_calls: [],
|
|
118
|
+
tool_call_chunks: [],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
run_attempt: 1,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
event: "messages",
|
|
127
|
+
data: [
|
|
128
|
+
{
|
|
129
|
+
id: "run-1",
|
|
130
|
+
content: " How may I assist you today?",
|
|
131
|
+
additional_kwargs: {},
|
|
132
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
133
|
+
type: "AIMessageChunk",
|
|
134
|
+
name: null,
|
|
135
|
+
tool_calls: [],
|
|
136
|
+
invalid_tool_calls: [],
|
|
137
|
+
tool_call_chunks: [],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
run_attempt: 1,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
const { result } = renderHook(() =>
|
|
147
|
+
useLangGraphMessages({
|
|
148
|
+
stream: mockStreamCallback,
|
|
149
|
+
appendMessage: appendLangChainChunk,
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
act(() => {
|
|
154
|
+
result.current.sendMessage(
|
|
155
|
+
[
|
|
156
|
+
{
|
|
157
|
+
type: "human",
|
|
158
|
+
content: "Hello!",
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
{},
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await waitFor(() => {
|
|
166
|
+
expect(result.current.messages.length).toEqual(2);
|
|
167
|
+
expect(result.current.messages[0].type).toEqual("human");
|
|
168
|
+
expect(result.current.messages[1].type).toEqual("ai");
|
|
169
|
+
expect(
|
|
170
|
+
(result.current.messages[1].content[0] as MessageContentText).type,
|
|
171
|
+
).toEqual("text");
|
|
172
|
+
expect(
|
|
173
|
+
(result.current.messages[1].content[0] as MessageContentText).text,
|
|
174
|
+
).toEqual("Hello! How may I assist you today?");
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("separates chunks w/ different ids", async () => {
|
|
179
|
+
const mockStreamCallback = mockStreamCallbackFactory([
|
|
180
|
+
metadataEvent,
|
|
181
|
+
{
|
|
182
|
+
event: "messages",
|
|
183
|
+
data: [
|
|
184
|
+
{
|
|
185
|
+
id: "run-1",
|
|
186
|
+
content: "",
|
|
187
|
+
additional_kwargs: {},
|
|
188
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
189
|
+
type: "AIMessageChunk",
|
|
190
|
+
name: null,
|
|
191
|
+
tool_calls: [],
|
|
192
|
+
invalid_tool_calls: [],
|
|
193
|
+
tool_call_chunks: [],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
run_attempt: 1,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
event: "messages",
|
|
202
|
+
data: [
|
|
203
|
+
{
|
|
204
|
+
id: "run-1",
|
|
205
|
+
content: "Hello!",
|
|
206
|
+
additional_kwargs: {},
|
|
207
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
208
|
+
type: "AIMessageChunk",
|
|
209
|
+
name: null,
|
|
210
|
+
tool_calls: [],
|
|
211
|
+
invalid_tool_calls: [],
|
|
212
|
+
tool_call_chunks: [],
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
run_attempt: 1,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
event: "messages",
|
|
221
|
+
data: [
|
|
222
|
+
{
|
|
223
|
+
id: "run-2",
|
|
224
|
+
content: " How may I assist you today?",
|
|
225
|
+
additional_kwargs: {},
|
|
226
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
227
|
+
type: "AIMessageChunk",
|
|
228
|
+
name: null,
|
|
229
|
+
tool_calls: [],
|
|
230
|
+
invalid_tool_calls: [],
|
|
231
|
+
tool_call_chunks: [],
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
run_attempt: 1,
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
const { result } = renderHook(() =>
|
|
241
|
+
useLangGraphMessages({
|
|
242
|
+
stream: mockStreamCallback,
|
|
243
|
+
appendMessage: appendLangChainChunk,
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
act(() => {
|
|
248
|
+
result.current.sendMessage(
|
|
249
|
+
[
|
|
250
|
+
{
|
|
251
|
+
type: "human",
|
|
252
|
+
content: "Hello!",
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
{},
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
await waitFor(() => {
|
|
260
|
+
expect(result.current.messages.length).toEqual(3);
|
|
261
|
+
expect(result.current.messages[0].type).toEqual("human");
|
|
262
|
+
expect(result.current.messages[1].type).toEqual("ai");
|
|
263
|
+
expect(result.current.messages[2].type).toEqual("ai");
|
|
264
|
+
expect(
|
|
265
|
+
(result.current.messages[1].content[0] as MessageContentText).type,
|
|
266
|
+
).toEqual("text");
|
|
267
|
+
expect(
|
|
268
|
+
(result.current.messages[1].content[0] as MessageContentText).text,
|
|
269
|
+
).toEqual("Hello!");
|
|
270
|
+
expect(result.current.messages[2].content as string).toEqual(
|
|
271
|
+
" How may I assist you today?",
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("handles a mix of text and image chunks - start with text", async () => {
|
|
277
|
+
const mockStreamCallback = mockStreamCallbackFactory([
|
|
278
|
+
metadataEvent,
|
|
279
|
+
{
|
|
280
|
+
event: "messages",
|
|
281
|
+
data: [
|
|
282
|
+
{
|
|
283
|
+
id: "run-1",
|
|
284
|
+
content: "",
|
|
285
|
+
additional_kwargs: {},
|
|
286
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
287
|
+
type: "AIMessageChunk",
|
|
288
|
+
name: null,
|
|
289
|
+
tool_calls: [],
|
|
290
|
+
invalid_tool_calls: [],
|
|
291
|
+
tool_call_chunks: [],
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
run_attempt: 1,
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
event: "messages",
|
|
300
|
+
data: [
|
|
301
|
+
{
|
|
302
|
+
id: "run-1",
|
|
303
|
+
content: "Hello!",
|
|
304
|
+
additional_kwargs: {},
|
|
305
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
306
|
+
type: "AIMessageChunk",
|
|
307
|
+
name: null,
|
|
308
|
+
tool_calls: [],
|
|
309
|
+
invalid_tool_calls: [],
|
|
310
|
+
tool_call_chunks: [],
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
run_attempt: 1,
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
event: "messages",
|
|
319
|
+
data: [
|
|
320
|
+
{
|
|
321
|
+
id: "run-1",
|
|
322
|
+
content: " How may I assist you today?",
|
|
323
|
+
additional_kwargs: {},
|
|
324
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
325
|
+
type: "AIMessageChunk",
|
|
326
|
+
name: null,
|
|
327
|
+
tool_calls: [],
|
|
328
|
+
invalid_tool_calls: [],
|
|
329
|
+
tool_call_chunks: [],
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
run_attempt: 1,
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
event: "messages",
|
|
338
|
+
data: [
|
|
339
|
+
{
|
|
340
|
+
id: "run-1",
|
|
341
|
+
content: [
|
|
342
|
+
{
|
|
343
|
+
type: "image_url",
|
|
344
|
+
image_url: { url: "https://example.com/image.png" },
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
type: "AIMessageChunk",
|
|
348
|
+
name: null,
|
|
349
|
+
tool_calls: [],
|
|
350
|
+
invalid_tool_calls: [],
|
|
351
|
+
tool_call_chunks: [],
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
run_attempt: 1,
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
},
|
|
358
|
+
]);
|
|
359
|
+
|
|
360
|
+
const { result } = renderHook(() =>
|
|
361
|
+
useLangGraphMessages({
|
|
362
|
+
stream: mockStreamCallback,
|
|
363
|
+
appendMessage: appendLangChainChunk,
|
|
364
|
+
}),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
act(() => {
|
|
368
|
+
result.current.sendMessage(
|
|
369
|
+
[
|
|
370
|
+
{
|
|
371
|
+
type: "human",
|
|
372
|
+
content: "Hello!",
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
{},
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
await waitFor(() => {
|
|
380
|
+
expect(result.current.messages.length).toEqual(2);
|
|
381
|
+
expect(result.current.messages[0].type).toEqual("human");
|
|
382
|
+
expect(result.current.messages[1].type).toEqual("ai");
|
|
383
|
+
expect(
|
|
384
|
+
(result.current.messages[1].content[0] as MessageContentText).type,
|
|
385
|
+
).toEqual("text");
|
|
386
|
+
expect(
|
|
387
|
+
(result.current.messages[1].content[0] as MessageContentText).text,
|
|
388
|
+
).toEqual("Hello! How may I assist you today?");
|
|
389
|
+
expect(
|
|
390
|
+
(result.current.messages[1].content[1] as MessageContentImageUrl).type,
|
|
391
|
+
).toEqual("image_url");
|
|
392
|
+
const imageChunkContent = result.current.messages[1]
|
|
393
|
+
.content[1] as MessageContentImageUrl;
|
|
394
|
+
expect(typeof imageChunkContent.image_url).toEqual("object");
|
|
395
|
+
expect(
|
|
396
|
+
(
|
|
397
|
+
(result.current.messages[1].content[1] as MessageContentImageUrl)
|
|
398
|
+
.image_url as { url: string }
|
|
399
|
+
).url,
|
|
400
|
+
).toEqual("https://example.com/image.png");
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("handles a mix of text and image chunks - start with image", async () => {
|
|
405
|
+
const mockStreamCallback = mockStreamCallbackFactory([
|
|
406
|
+
metadataEvent,
|
|
407
|
+
{
|
|
408
|
+
event: "messages",
|
|
409
|
+
data: [
|
|
410
|
+
{
|
|
411
|
+
id: "run-1",
|
|
412
|
+
content: [
|
|
413
|
+
{
|
|
414
|
+
type: "image_url",
|
|
415
|
+
image_url: { url: "https://example.com/image.png" },
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
type: "AIMessageChunk",
|
|
419
|
+
name: null,
|
|
420
|
+
tool_calls: [],
|
|
421
|
+
invalid_tool_calls: [],
|
|
422
|
+
tool_call_chunks: [],
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
run_attempt: 1,
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
event: "messages",
|
|
431
|
+
data: [
|
|
432
|
+
{
|
|
433
|
+
id: "run-1",
|
|
434
|
+
content: "",
|
|
435
|
+
additional_kwargs: {},
|
|
436
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
437
|
+
type: "AIMessageChunk",
|
|
438
|
+
name: null,
|
|
439
|
+
tool_calls: [],
|
|
440
|
+
invalid_tool_calls: [],
|
|
441
|
+
tool_call_chunks: [],
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
run_attempt: 1,
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
event: "messages",
|
|
450
|
+
data: [
|
|
451
|
+
{
|
|
452
|
+
id: "run-1",
|
|
453
|
+
content: "Hello!",
|
|
454
|
+
additional_kwargs: {},
|
|
455
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
456
|
+
type: "AIMessageChunk",
|
|
457
|
+
name: null,
|
|
458
|
+
tool_calls: [],
|
|
459
|
+
invalid_tool_calls: [],
|
|
460
|
+
tool_call_chunks: [],
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
run_attempt: 1,
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
event: "messages",
|
|
469
|
+
data: [
|
|
470
|
+
{
|
|
471
|
+
id: "run-1",
|
|
472
|
+
content: " How may I assist you today?",
|
|
473
|
+
additional_kwargs: {},
|
|
474
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
475
|
+
type: "AIMessageChunk",
|
|
476
|
+
name: null,
|
|
477
|
+
tool_calls: [],
|
|
478
|
+
invalid_tool_calls: [],
|
|
479
|
+
tool_call_chunks: [],
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
run_attempt: 1,
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
},
|
|
486
|
+
]);
|
|
487
|
+
|
|
488
|
+
const { result } = renderHook(() =>
|
|
489
|
+
useLangGraphMessages({
|
|
490
|
+
stream: mockStreamCallback,
|
|
491
|
+
appendMessage: appendLangChainChunk,
|
|
492
|
+
}),
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
act(() => {
|
|
496
|
+
result.current.sendMessage(
|
|
497
|
+
[
|
|
498
|
+
{
|
|
499
|
+
type: "human",
|
|
500
|
+
content: "Hello!",
|
|
501
|
+
},
|
|
502
|
+
],
|
|
503
|
+
{},
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
await waitFor(() => {
|
|
508
|
+
expect(result.current.messages.length).toEqual(2);
|
|
509
|
+
expect(result.current.messages[0].type).toEqual("human");
|
|
510
|
+
expect(result.current.messages[1].type).toEqual("ai");
|
|
511
|
+
expect(
|
|
512
|
+
(result.current.messages[1].content[0] as MessageContentImageUrl).type,
|
|
513
|
+
).toEqual("image_url");
|
|
514
|
+
const imageChunkContent = result.current.messages[1]
|
|
515
|
+
.content[0] as MessageContentImageUrl;
|
|
516
|
+
expect(typeof imageChunkContent.image_url).toEqual("object");
|
|
517
|
+
expect(
|
|
518
|
+
(
|
|
519
|
+
(result.current.messages[1].content[0] as MessageContentImageUrl)
|
|
520
|
+
.image_url as { url: string }
|
|
521
|
+
).url,
|
|
522
|
+
).toEqual("https://example.com/image.png");
|
|
523
|
+
expect(
|
|
524
|
+
(result.current.messages[1].content[1] as MessageContentText).type,
|
|
525
|
+
).toEqual("text");
|
|
526
|
+
expect(
|
|
527
|
+
(result.current.messages[1].content[1] as MessageContentText).text,
|
|
528
|
+
).toEqual("Hello! How may I assist you today?");
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("processes a mix of chunks and messages", async () => {
|
|
533
|
+
const mockStreamCallback = mockStreamCallbackFactory([
|
|
534
|
+
metadataEvent,
|
|
535
|
+
{
|
|
536
|
+
event: "messages",
|
|
537
|
+
data: [
|
|
538
|
+
{
|
|
539
|
+
id: "run-1",
|
|
540
|
+
content: "",
|
|
541
|
+
additional_kwargs: {},
|
|
542
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
543
|
+
type: "AIMessageChunk",
|
|
544
|
+
name: null,
|
|
545
|
+
tool_calls: [],
|
|
546
|
+
invalid_tool_calls: [],
|
|
547
|
+
tool_call_chunks: [],
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
run_attempt: 1,
|
|
551
|
+
},
|
|
552
|
+
],
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
event: "messages",
|
|
556
|
+
data: [
|
|
557
|
+
{
|
|
558
|
+
id: "run-1",
|
|
559
|
+
content: "Hello!",
|
|
560
|
+
additional_kwargs: {},
|
|
561
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
562
|
+
type: "AIMessageChunk",
|
|
563
|
+
name: null,
|
|
564
|
+
tool_calls: [],
|
|
565
|
+
invalid_tool_calls: [],
|
|
566
|
+
tool_call_chunks: [],
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
run_attempt: 1,
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
event: "messages/complete",
|
|
575
|
+
data: [
|
|
576
|
+
{
|
|
577
|
+
id: "run-2",
|
|
578
|
+
content: [{ type: "text", text: "How may I assist you today?" }],
|
|
579
|
+
additional_kwargs: {},
|
|
580
|
+
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
|
581
|
+
type: "ai",
|
|
582
|
+
name: null,
|
|
583
|
+
tool_calls: [],
|
|
584
|
+
invalid_tool_calls: [],
|
|
585
|
+
tool_call_chunks: [],
|
|
586
|
+
},
|
|
587
|
+
],
|
|
588
|
+
},
|
|
589
|
+
]);
|
|
590
|
+
|
|
591
|
+
const { result } = renderHook(() =>
|
|
592
|
+
useLangGraphMessages({
|
|
593
|
+
stream: mockStreamCallback,
|
|
594
|
+
appendMessage: appendLangChainChunk,
|
|
595
|
+
}),
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
act(() => {
|
|
599
|
+
result.current.sendMessage(
|
|
600
|
+
[
|
|
601
|
+
{
|
|
602
|
+
type: "human",
|
|
603
|
+
content: "Hello!",
|
|
604
|
+
},
|
|
605
|
+
],
|
|
606
|
+
{},
|
|
607
|
+
);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
await waitFor(() => {
|
|
611
|
+
expect(result.current.messages.length).toEqual(3);
|
|
612
|
+
expect(result.current.messages[0].type).toEqual("human");
|
|
613
|
+
expect(result.current.messages[1].type).toEqual("ai");
|
|
614
|
+
expect(result.current.messages[2].type).toEqual("ai");
|
|
615
|
+
expect(
|
|
616
|
+
(result.current.messages[1].content[0] as MessageContentText).type,
|
|
617
|
+
).toEqual("text");
|
|
618
|
+
expect(
|
|
619
|
+
(result.current.messages[1].content[0] as MessageContentText).text,
|
|
620
|
+
).toEqual("Hello!");
|
|
621
|
+
expect(
|
|
622
|
+
(result.current.messages[2].content[0] as MessageContentText).text,
|
|
623
|
+
).toEqual("How may I assist you today?");
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
});
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { useState, useCallback, useRef } from "react";
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
3
|
import { LangGraphMessageAccumulator } from "./LangGraphMessageAccumulator";
|
|
4
|
+
import {
|
|
5
|
+
EventType,
|
|
6
|
+
LangChainMessageTupleEvent,
|
|
7
|
+
LangGraphKnownEventTypes,
|
|
8
|
+
LangChainMessageChunk,
|
|
9
|
+
} from "./types";
|
|
4
10
|
|
|
5
11
|
export type LangGraphCommand = {
|
|
6
12
|
resume: string;
|
|
@@ -12,15 +18,10 @@ export type LangGraphSendMessageConfig = {
|
|
|
12
18
|
};
|
|
13
19
|
|
|
14
20
|
export type LangGraphMessagesEvent<TMessage> = {
|
|
15
|
-
event:
|
|
16
|
-
| "messages"
|
|
17
|
-
| "messages/partial"
|
|
18
|
-
| "messages/complete"
|
|
19
|
-
| "metadata"
|
|
20
|
-
| "updates"
|
|
21
|
-
| string;
|
|
21
|
+
event: EventType;
|
|
22
22
|
data: TMessage[] | any;
|
|
23
23
|
};
|
|
24
|
+
|
|
24
25
|
export type LangGraphStreamCallback<TMessage> = (
|
|
25
26
|
messages: TMessage[],
|
|
26
27
|
config: LangGraphSendMessageConfig & { abortSignal: AbortSignal },
|
|
@@ -40,6 +41,22 @@ const DEFAULT_APPEND_MESSAGE = <TMessage>(
|
|
|
40
41
|
curr: TMessage,
|
|
41
42
|
) => curr;
|
|
42
43
|
|
|
44
|
+
const isLangChainMessageChunk = (
|
|
45
|
+
value: unknown,
|
|
46
|
+
): value is LangChainMessageChunk => {
|
|
47
|
+
if (!value || typeof value !== "object") return false;
|
|
48
|
+
const chunk = value as any;
|
|
49
|
+
return (
|
|
50
|
+
"type" in chunk &&
|
|
51
|
+
chunk.type === "AIMessageChunk" &&
|
|
52
|
+
(chunk.content === undefined ||
|
|
53
|
+
typeof chunk.content === "string" ||
|
|
54
|
+
Array.isArray(chunk.content)) &&
|
|
55
|
+
(chunk.tool_call_chunks === undefined ||
|
|
56
|
+
Array.isArray(chunk.tool_call_chunks))
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
43
60
|
export const useLangGraphMessages = <TMessage extends { id?: string }>({
|
|
44
61
|
stream,
|
|
45
62
|
appendMessage = DEFAULT_APPEND_MESSAGE,
|
|
@@ -56,29 +73,52 @@ export const useLangGraphMessages = <TMessage extends { id?: string }>({
|
|
|
56
73
|
const sendMessage = useCallback(
|
|
57
74
|
async (newMessages: TMessage[], config: LangGraphSendMessageConfig) => {
|
|
58
75
|
// ensure all messages have an ID
|
|
59
|
-
|
|
76
|
+
const newMessagesWithId = newMessages.map((m) =>
|
|
77
|
+
m.id ? m : { ...m, id: uuidv4() },
|
|
78
|
+
);
|
|
60
79
|
|
|
61
80
|
const accumulator = new LangGraphMessageAccumulator({
|
|
62
81
|
initialMessages: messages,
|
|
63
82
|
appendMessage,
|
|
64
83
|
});
|
|
65
|
-
setMessages(accumulator.addMessages(
|
|
84
|
+
setMessages(accumulator.addMessages(newMessagesWithId));
|
|
66
85
|
|
|
67
86
|
const abortController = new AbortController();
|
|
68
87
|
abortControllerRef.current = abortController;
|
|
69
|
-
const response = await stream(
|
|
88
|
+
const response = await stream(newMessagesWithId, {
|
|
70
89
|
...config,
|
|
71
90
|
abortSignal: abortController.signal,
|
|
72
91
|
});
|
|
73
92
|
|
|
74
93
|
for await (const chunk of response) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
switch (chunk.event) {
|
|
95
|
+
case LangGraphKnownEventTypes.MessagesPartial:
|
|
96
|
+
case LangGraphKnownEventTypes.MessagesComplete:
|
|
97
|
+
setMessages(accumulator.addMessages(chunk.data));
|
|
98
|
+
break;
|
|
99
|
+
case LangGraphKnownEventTypes.Updates:
|
|
100
|
+
setInterrupt(chunk.data.__interrupt__?.[0]);
|
|
101
|
+
break;
|
|
102
|
+
case LangGraphKnownEventTypes.Messages: {
|
|
103
|
+
const [messageChunk] = (chunk as LangChainMessageTupleEvent).data;
|
|
104
|
+
if (!isLangChainMessageChunk(messageChunk)) {
|
|
105
|
+
console.warn(
|
|
106
|
+
"Received invalid message chunk format:",
|
|
107
|
+
messageChunk,
|
|
108
|
+
);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
const updatedMessages = accumulator.addMessages([
|
|
112
|
+
messageChunk as unknown as TMessage,
|
|
113
|
+
]);
|
|
114
|
+
setMessages(updatedMessages);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case LangGraphKnownEventTypes.Metadata:
|
|
118
|
+
// currently this is a no-op
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
console.warn(`The event type ${chunk.event} is not supported.`);
|
|
82
122
|
}
|
|
83
123
|
}
|
|
84
124
|
},
|