2025-02-28 14:15:37 -08:00
|
|
|
import { ReactNode, useEffect } from "react";
|
2025-02-26 17:09:16 -08:00
|
|
|
import {
|
|
|
|
|
useExternalStoreRuntime,
|
|
|
|
|
AppendMessage,
|
|
|
|
|
AssistantRuntimeProvider,
|
|
|
|
|
} from "@assistant-ui/react";
|
2025-02-28 14:15:37 -08:00
|
|
|
import { HumanMessage, Message, ToolMessage } from "@langchain/langgraph-sdk";
|
2025-02-27 14:08:24 -08:00
|
|
|
import { useStreamContext } from "./Stream";
|
|
|
|
|
import { convertLangChainMessages } from "./convert-messages";
|
2025-02-26 17:09:16 -08:00
|
|
|
|
2025-02-28 14:15:37 -08:00
|
|
|
function ensureToolCallsHaveResponses(messages: Message[]): Message[] {
|
|
|
|
|
const newMessages: ToolMessage[] = [];
|
|
|
|
|
|
|
|
|
|
messages.forEach((message, index) => {
|
|
|
|
|
if (message.type !== "ai" || message.tool_calls?.length === 0) {
|
|
|
|
|
// If it's not an AI message, or it doesn't have tool calls, we can ignore.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// If it has tool calls, ensure the message which follows this is a tool message
|
|
|
|
|
const followingMessage = messages[index + 1];
|
|
|
|
|
if (followingMessage && followingMessage.type === "tool") {
|
|
|
|
|
// Following message is a tool message, so we can ignore.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Since the following message is not a tool message, we must create a new tool message
|
|
|
|
|
newMessages.push(
|
|
|
|
|
...(message.tool_calls?.map((tc) => ({
|
|
|
|
|
type: "tool" as const,
|
|
|
|
|
tool_call_id: tc.id ?? "",
|
|
|
|
|
id: tc.id ?? "",
|
|
|
|
|
name: tc.name,
|
|
|
|
|
content: "Successfully handled tool call.",
|
2025-03-03 14:52:23 +01:00
|
|
|
})) ?? [])
|
2025-02-28 14:15:37 -08:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return newMessages;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 17:09:16 -08:00
|
|
|
export function RuntimeProvider({
|
|
|
|
|
children,
|
|
|
|
|
}: Readonly<{
|
|
|
|
|
children: ReactNode;
|
|
|
|
|
}>) {
|
2025-02-27 14:08:24 -08:00
|
|
|
const stream = useStreamContext();
|
|
|
|
|
|
2025-02-26 17:09:16 -08:00
|
|
|
const onNew = async (message: AppendMessage) => {
|
|
|
|
|
if (message.content[0]?.type !== "text")
|
|
|
|
|
throw new Error("Only text messages are supported");
|
2025-02-27 14:08:24 -08:00
|
|
|
|
2025-02-26 17:09:16 -08:00
|
|
|
const input = message.content[0].text;
|
2025-02-27 14:08:24 -08:00
|
|
|
const humanMessage: HumanMessage = { type: "human", content: input };
|
2025-02-28 14:15:37 -08:00
|
|
|
const newMessages = [
|
|
|
|
|
...ensureToolCallsHaveResponses(stream.messages),
|
|
|
|
|
humanMessage,
|
|
|
|
|
];
|
|
|
|
|
console.log("Sending new messages", newMessages);
|
2025-03-03 14:52:23 +01:00
|
|
|
stream.submit({ messages: newMessages }, { streamMode: ["values"] });
|
2025-02-26 17:09:16 -08:00
|
|
|
};
|
2025-02-27 14:08:24 -08:00
|
|
|
|
2025-02-28 14:15:37 -08:00
|
|
|
useEffect(() => {
|
|
|
|
|
console.log("useEffect - stream.messages", stream.messages);
|
|
|
|
|
}, [stream.messages]);
|
|
|
|
|
|
2025-02-26 17:09:16 -08:00
|
|
|
const runtime = useExternalStoreRuntime({
|
2025-02-27 14:08:24 -08:00
|
|
|
isRunning: stream.isLoading,
|
|
|
|
|
messages: stream.messages,
|
|
|
|
|
convertMessage: convertLangChainMessages,
|
2025-02-26 17:09:16 -08:00
|
|
|
onNew,
|
|
|
|
|
});
|
2025-02-27 14:08:24 -08:00
|
|
|
|
2025-02-26 17:09:16 -08:00
|
|
|
return (
|
|
|
|
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
|
|
|
{children}
|
|
|
|
|
</AssistantRuntimeProvider>
|
|
|
|
|
);
|
2025-02-27 14:08:24 -08:00
|
|
|
}
|